ভূমিকা
সাম্প্রতিক বছরগুলিতে, ওয়েব অ্যাপ্লিকেশনগুলি যথেষ্ট দ্রুত হয়েছে। অনেক অ্যাপ্লিকেশন এখন যথেষ্ট দ্রুত চলে, তাই আমি কিছু ডেভেলপারকে জোরে জোরে বলতে শুনেছি "ওয়েব কি যথেষ্ট দ্রুত?"। কিছু অ্যাপ্লিকেশনের ক্ষেত্রে এটি হতে পারে, তবে, হাই পারফর্ম্যান্স অ্যাপ্লিকেশনে কাজ করা ডেভেলপারদের জন্য, আমরা জানি যে এটি যথেষ্ট দ্রুত নয়। JavaScript ভার্চুয়াল মেশিন টেকনোলজিতে অসাধারণ উন্নতি হওয়া সত্ত্বেও, একটি সাম্প্রতিক সমীক্ষা থেকে দেখা গেছে যে Google অ্যাপ্লিকেশনগুলি তাদের সময়ের ৫০% থেকে ৭০% V8-এর মধ্যে ব্যয় করে। আপনার অ্যাপ্লিকেশনের জন্য সীমিত সময় আছে, একটি সিস্টেমের সাইকেল কমানো মানে অন্য সিস্টেম আরও বেশি কাজ করতে পারবে। মনে রাখবেন, ৬০fps-এ চলা অ্যাপ্লিকেশনে ফ্রেম প্রতি ১৬ms থাকে, না হলে - জ্যাঙ্ক। জাভাস্ক্রিপ্ট অপ্টিমাইজ করা এবং জাভাস্ক্রিপ্ট অ্যাপ্লিকেশন প্রোফাইল করার বিষয়ে জানতে, V8 টিমের পারফর্ম্যান্স ডিটেকটিভদের একটি গল্প পড়ুন, যারা Find Your Way to Oz-এ একটি অস্পষ্ট পারফর্ম্যান্স সমস্যা ট্র্যাক করছেন।
Google I/O 2013 সেশন
আমি Google I/O 2013-এ এই মেটেরিয়ালটি উপস্থাপন করেছি। নিচের ভিডিওটি দেখুন:
পারফর্ম্যান্স কেন গুরুত্বপূর্ণ?
সিপিইউ (CPU) সাইকেল হল একটি জিরো-সাম গেম। আপনার সিস্টেমের একটি অংশ কম ব্যবহার করলে, আপনি অন্য অংশে বেশি ব্যবহার করতে পারবেন বা সামগ্রিকভাবে আরও মসৃণভাবে চালাতে পারবেন। আরও দ্রুত রান করা এবং আরও বেশি কাজ করা প্রায়শই একে অপরের সাথে প্রতিযোগিতা করে। ব্যবহারকারীরা নতুন ফিচার চান এবং একই সাথে আপনার অ্যাপ্লিকেশন যাতে আরও মসৃণভাবে রান করে সেই প্রত্যাশাও করেন। জাভাস্ক্রিপ্ট ভার্চুয়াল মেশিন আরও দ্রুত কাজ করছে, তবে এর অর্থ এই নয় যে আপনি পারফর্ম্যান্স সংক্রান্ত সমস্যা উপেক্ষা করবেন, কারণ অনেক ডেভেলপারই জানেন যে ওয়েব অ্যাপ্লিকেশনে পারফর্ম্যান্স সংক্রান্ত সমস্যা কীভাবে সমাধান করতে হয়। রিয়েল-টাইম, হাই ফ্রেম রেট, অ্যাপ্লিকেশনে জ্যাঙ্ক-ফ্রি থাকার চাপ সবচেয়ে বেশি। Insomniac Games একটি স্টাডি প্রকাশ করেছে যা দেখায় যে কোনও গেমের সাফল্যের জন্য একটি সলিড, টেকসই ফ্রেম রেট গুরুত্বপূর্ণ: "একটি সলিড ফ্রেম-রেট এখনও একটি পেশাদার, ভালভাবে তৈরি প্রোডাক্টের লক্ষণ।" ওয়েব ডেভেলপাররা নোট নিন।
পারফর্ম্যান্স সংক্রান্ত সমস্যার সমাধান করা
পারফর্ম্যান্স সংক্রান্ত সমস্যার সমাধান করা অপরাধের তদন্ত করার মতোই। আপনাকে প্রমাণগুলি যত্ন সহকারে পরীক্ষা করতে হবে, সন্দেহজনক কারণগুলি চেক করতে হবে এবং বিভিন্ন সমাধান নিয়ে পরীক্ষা করতে হবে। সমস্যাটি যে আপনি সত্যিই সমাধান করেছেন তা নিশ্চিত করার জন্য, আপনাকে অবশ্যই পুরো প্রক্রিয়া জুড়ে আপনার পরিমাপের ডকুমেন্টেশন করতে হবে। এই পদ্ধতি এবং অপরাধের তদন্তকারী গোয়েন্দারা যেভাবে কোনও কেসের সমাধান করেন, তার মধ্যে খুব কমই পার্থক্য আছে। ডিটেকটিভরা প্রমাণ পরীক্ষা করেন, সন্দেহভাজনদের জিজ্ঞাসাবাদ করেন এবং অপরাধের অকাট্য প্রমাণ খুঁজে পাওয়ার আশায় পরীক্ষা-নিরীক্ষা করেন।
V8 CSI: Oz
Find Your Way to Oz তৈরি করা অসাধারণ উইজার্ডরা পারফর্ম্যান্স সংক্রান্ত একটি সমস্যা নিয়ে V8 টিমের সাথে যোগাযোগ করেছিলেন, যা তারা নিজেরাই সমাধান করতে পারেননি। মাঝে মাঝে Oz ফ্রিজ হয়ে যায়, যার ফলে জ্যাঙ্ক হয়। Oz ডেভেলপাররা Chrome DevTools-এ টাইমলাইন প্যানেল ব্যবহার করে কিছু প্রাথমিক তদন্ত করেছিলেন। মেমরির ব্যবহার দেখার সময় তারা ভয়ঙ্কর করাতের দাঁতের মতো গ্রাফ দেখতে পান। প্রতি সেকেন্ডে একবার করে গার্বেজ কালেক্টর ১০ এমবি গার্বেজ সংগ্রহ করছিল এবং গার্বেজ কালেকশন পজ করার সাথে সাথে জ্যাঙ্কও পজ হয়ে যাচ্ছিল। Chrome Devtools-এর টাইমলাইন থেকে নেওয়া নিম্নলিখিত স্ক্রিনশটের মতো:
V8 ডিটেক্টিভ, জ্যাকব এবং ইয়াং কেসটি হাতে নিলেন। V8 টিম থেকে জ্যাকব ও ইয়াং এবং Oz টিমের মধ্যে দীর্ঘ আলোচনা হয়। এই কথোপকথন থেকে আমি গুরুত্বপূর্ণ ইভেন্টগুলি বেছে নিয়েছি যা এই সমস্যাটি ট্র্যাক করতে সাহায্য করেছে।
প্রমাণ
প্রথম ধাপ হল প্রাথমিক প্রমাণ সংগ্রহ ও তা নিয়ে পড়াশোনা করা।
আমরা কোন ধরনের অ্যাপ্লিকেশন দেখছি?
Oz ডেমো হল একটি ইন্টার্যাক্টিভ 3D অ্যাপ্লিকেশন। এই কারণে, এটি গার্বেজ কালেকশনের কারণে হওয়া পজের প্রতি অত্যন্ত সংবেদনশীল। মনে রাখবেন, 60fps-এ চলা ইন্টার্যাক্টিভ অ্যাপ্লিকেশনের কাছে জাভাস্ক্রিপ্টের সব কাজ করার জন্য ১৬ms সময় থাকে এবং এর মধ্যে কিছুটা সময় Chrome-এর জন্য ছেড়ে দিতে হয় যাতে সেটি গ্রাফিক্স কল প্রসেস করতে এবং স্ক্রিন ড্র করতে পারে।
Oz ডবল ভ্যালুতে অনেক পাটিগণিত গণনা করে এবং WebAudio ও WebGL-এ ঘন ঘন কল করে।
পারফর্ম্যান্স সংক্রান্ত কী ধরনের সমস্যা আমরা দেখতে পাচ্ছি?
আমরা পজ, ফ্রেম ড্রপ বা জ্যাঙ্ক দেখতে পাচ্ছি। এইসব পজ গার্বেজ কালেকশন রানের সাথে সম্পর্কিত।
ডেভেলপাররা কি পেশাদার পদ্ধতি অনুসরণ করছেন?
হ্যাঁ, Oz ডেভেলপাররা জাভাস্ক্রিপ্ট VM পারফর্ম্যান্স এবং অপ্টিমাইজেশন টেকনিক সম্পর্কে ভালভাবে জানেন। এটি উল্লেখ করা যেতে পারে যে Oz ডেভেলপাররা তাদের সোর্স ল্যাঙ্গুয়েজ হিসেবে CoffeeScript ব্যবহার করছিলেন এবং CoffeeScript কম্পাইলারের মাধ্যমে JavaScript কোড তৈরি করছিলেন। এর ফলে কিছু তদন্ত করা আরও কঠিন হয়ে পড়েছিল কারণ Oz ডেভেলপারদের লেখা কোড এবং V8-এর ব্যবহার করা কোডের মধ্যে কোনও সংযোগ ছিল না। Chrome DevTools এখন সোর্স ম্যাপ সাপোর্ট করে যা এটিকে আরও সহজ করে তুলবে।
গার্বেজ কালেক্টর কেন রান করে?
জাভাস্ক্রিপ্টে মেমরি VM-এর মাধ্যমে ডেভেলপারের জন্য অটোমেটিক ম্যানেজ করা হয়। V8 একটি সাধারণ গার্বেজ কালেকশন সিস্টেম ব্যবহার করে যেখানে মেমরি দুই (বা তার বেশি) জেনারেশনে বিভক্ত। নতুন জেনারেশন সেইসব অবজেক্ট ধরে রাখে যেগুলি সম্প্রতি বরাদ্দ করা হয়েছে। কোনও অবজেক্ট অনেকদিন ধরে থাকলে, সেটি পুরনো জেনারেশনে সরানো হয়।
পুরনো জেনারেশনের তুলনায় নতুন জেনারেশন অনেক বেশি ফ্রিকোয়েন্সিতে সংগ্রহ করা হয়। এটি ডিজাইন অনুযায়ী, কারণ তরুণ প্রজন্মের সংগ্রহ অনেক সস্তা। এটি ধরে নেওয়া নিরাপদ যে ঘন ঘন GC পজ হওয়ার কারণ হল ইয়াং জেনারেশন কালেকশন।
V8-এ, ইয়াং মেমরি স্পেসকে দুটি সমান আকারের সংলগ্ন মেমরি ব্লকে ভাগ করা হয়। যেকোনও সময়ে মেমরির এই দুটি ব্লকের মধ্যে একটিই ব্যবহার করা হয় এবং এটিকে 'টু স্পেস' বলা হয়। to স্পেসে মেমরি অবশিষ্ট থাকলে, নতুন অবজেক্ট অ্যালোকেট করা সস্তা। নতুন অবজেক্টের জন্য যত বাইট প্রয়োজন, কার্সরটি তত বাইট সামনের দিকে সরানো হয়। যতক্ষণ না পর্যন্ত গন্তব্য স্পেস শেষ হয়ে যাচ্ছে, ততক্ষণ এই প্রসেস চলতে থাকে। এই সময় প্রোগ্রাম বন্ধ হয়ে যায় এবং সংগ্রহ শুরু হয়।
এই পয়েন্টে, 'থেকে' স্পেস ও 'পর্যন্ত' স্পেস সোয়্যাপ করা হয়। যেটি আগে 'টু স্পেস' ছিল এবং এখন 'ফ্রম স্পেস' হয়েছে, সেটি শুরু থেকে শেষ পর্যন্ত স্ক্যান করা হয় এবং এখনও জীবিত আছে এমন যেকোনও অবজেক্ট 'টু স্পেস'-এ কপি করা হয় বা পুরনো জেনারেশন হিপে প্রোমোট করা হয়। আপনি যদি বিস্তারিত জানতে চান, তাহলে আমি আপনাকে চেনির অ্যালগরিদম সম্পর্কে পড়তে সাজেস্ট করছি।
আপনাকে বুঝতে হবে যে প্রতিবার কোনও অবজেক্ট অন্তর্নিহিত বা বাহ্যিকভাবে (new, [], বা {} কল করার মাধ্যমে) বরাদ্দ করা হলে, আপনার অ্যাপ্লিকেশনটি গার্বেজ কালেকশন এবং অ্যাপ্লিকেশন পজ হওয়ার মতো ভয়ঙ্কর পরিস্থিতির দিকে এগিয়ে যায়।
এই অ্যাপ্লিকেশনের জন্য কি ১০এমবি/সেকেন্ডের গার্বেজ প্রত্যাশিত?
সংক্ষেপে, না। ডেভেলপার এমন কিছু করছেন না যাতে ১০ এমবি/সেকেন্ডের গার্বেজ আশা করা যায়।
সন্দেহভাজন
তদন্তের পরবর্তী ধাপ হল সম্ভাব্য সন্দেহভাজনদের নির্ধারণ করা এবং তারপরে তাদের সংখ্যা কমিয়ে আনা।
সন্দেহভাজন #১
ফ্রেম চলাকালীন নতুন কল করা হচ্ছে। মনে রাখবেন যে প্রতিটি অবজেক্ট যা বরাদ্দ করা হয় তা আপনাকে GC পজের আরও কাছাকাছি নিয়ে যায়। বিশেষ করে হাই ফ্রেম রেটে চলা অ্যাপ্লিকেশনগুলির ক্ষেত্রে ফ্রেম প্রতি জিরো অ্যালোকেশন করার চেষ্টা করা উচিত। সাধারণত এর জন্য সাবধানে চিন্তা করে তৈরি করা, অ্যাপ্লিকেশন-নির্দিষ্ট, অবজেক্ট রিসাইক্লিং সিস্টেমের প্রয়োজন হয়। V8 ডিটেকটিভ Oz টিমের সাথে চেক করে দেখেছে এবং তারা নতুন করে কল করেনি। আসলে Oz টিম আগে থেকেই এই প্রয়োজনীয়তা সম্পর্কে জানত এবং বলেছিল "সেটা লজ্জাজনক হবে"। তালিকা থেকে এটি বাদ দিন।
সন্দেহভাজন #২
কনস্ট্রাক্টরের বাইরে কোনও অবজেক্টের "আকৃতি" পরিবর্তন করা। কনস্ট্রাক্টরের বাইরে কোনও অবজেক্টে নতুন প্রপার্টি যোগ করা হলে এটি ঘটে। এটি অবজেক্টের জন্য একটি নতুন লুকানো ক্লাস তৈরি করে। অপ্টিমাইজ করা কোড যখন এই নতুন লুকানো ক্লাস দেখে, তখন একটি ডিঅপ্ট ট্রিগার হবে, কোডটি হট হিসেবে শ্রেণীবদ্ধ না হওয়া পর্যন্ত এবং আবার অপ্টিমাইজ না হওয়া পর্যন্ত অপ্টিমাইজ না করা কোড কার্যকর হবে। এই ডি-অপ্টিমাইজেশন,রি-অপ্টিমাইজেশন চার্নের ফলে জ্যাঙ্ক হবে কিন্তু অতিরিক্ত গার্বেজ তৈরির সাথে কঠোরভাবে সম্পর্কযুক্ত নয়। কোডটি যত্ন সহকারে অডিট করার পরে, এটি নিশ্চিত করা হয়েছিল যে অবজেক্টের আকারগুলি স্ট্যাটিক ছিল, এইভাবে সন্দেহ #2 বাতিল করা হয়েছিল।
সন্দেহভাজন #৩
অপ্টিমাইজ না করা কোডে পাটিগণিত। অপ্টিমাইজ না করা কোডে, সব কম্পিউটেশন ফলাফলের জন্য আসল অবজেক্ট বরাদ্দ করা হয়। যেমন, এই স্নিপেট:
var a = p * d;
var b = c + 3;
var c = 3.3 * dt;
point.x = a * b * c;
এর ফলে ৫টি HeapNumber অবজেক্ট তৈরি হয়। প্রথম তিনটি হল ভেরিয়েবল, a, b এবং c-এর জন্য। ৪র্থটি হল বেনামী ভ্যালুর জন্য (a * b) এবং ৫মটি হল #৪ * c থেকে; ৫মটি শেষ পর্যন্ত point.x-এ অ্যাসাইন করা হয়।
Oz প্রতি ফ্রেমে এই ধরনের হাজার হাজার অপারেশন করে। এইসব কম্পিউটেশনের কোনওটি যদি এমন ফাংশনে হয় যা কখনও অপ্টিমাইজ করা হয়নি, তাহলে সেগুলি গার্বেজের কারণ হতে পারে। কারণ, অপ্টিমাইজ না করা কম্পিউটেশন সাময়িক ফলাফলের জন্যও মেমরি বরাদ্দ করে।
সন্দেহভাজন #৪
কোনও প্রপার্টিতে ডবল প্রিসিশন নম্বর স্টোর করা। নম্বর স্টোর করার জন্য HeapNumber অবজেক্ট তৈরি করতে হবে এবং এই নতুন অবজেক্টে পয়েন্ট করার জন্য প্রপার্টি পরিবর্তন করতে হবে। HeapNumber-এ পয়েন্ট করার জন্য প্রপার্টি পরিবর্তন করলে কোনও গার্বেজ তৈরি হবে না। তবে, অবজেক্ট প্রপার্টি হিসেবে অনেক ডবল প্রিসিশন নম্বর স্টোর করা থাকতে পারে। কোডটি নিম্নলিখিত বিবৃতির মতো বিবৃতিতে পূর্ণ:
sprite.position.x += 0.5 * (dt);
অপ্টিমাইজ করা কোডে, প্রতিবার x-কে একটি নতুন গণনা করা ভ্যালু অ্যাসাইন করা হয়, যা আপাতদৃষ্টিতে নিরীহ স্টেটমেন্ট, একটি নতুন HeapNumber অবজেক্ট অন্তর্নিহিতভাবে বরাদ্দ করা হয়, যা আমাদের গার্বেজ কালেকশন পজের কাছাকাছি নিয়ে আসে।
মনে রাখবেন যে টাইপ করা অ্যারে (বা রেগুলার অ্যারে যাতে শুধু ডবল থাকে) ব্যবহার করে আপনি এই নির্দিষ্ট সমস্যাটি সম্পূর্ণভাবে এড়াতে পারবেন কারণ ডবল প্রিসিশন নম্বরের জন্য স্টোরেজ শুধুমাত্র একবার বরাদ্দ করা হয় এবং বারবার ভ্যালু পরিবর্তন করার জন্য নতুন স্টোরেজ বরাদ্দ করার প্রয়োজন হয় না।
সন্দেহভাজন #৪-এর সম্ভাবনা আছে।
ফরেনসিক
এই পর্যায়ে গোয়েন্দারা দুটি সম্ভাব্য সন্দেহভাজনকে চিহ্নিত করেছেন: অবজেক্ট প্রপার্টি হিসেবে হিপ নম্বর স্টোর করা এবং অপ্টিমাইজ না করা ফাংশনের মধ্যে অ্যারিথমেটিক কম্পিউটেশন হওয়া। এবার ল্যাবে গিয়ে সন্দেহভাজনদের মধ্যে কে অপরাধী তা নিশ্চিতভাবে নির্ধারণ করার সময় এসে গেছে। মনে রাখবেন: এই বিভাগে আমি আসল Oz সোর্স কোডে পাওয়া সমস্যার একটি রিপ্রোডাকশন ব্যবহার করব। এই রিপ্রোডাকশন মূল কোডের থেকে অনেক ছোট, তাই এটি সম্পর্কে যুক্তি দেওয়া সহজ।
এক্সপেরিমেন্ট #১
সন্দেহজনক #৩ (অপ্টিমাইজ না করা ফাংশনের মধ্যে পাটিগণিত গণনা) চেক করা হচ্ছে। V8 জাভাস্ক্রিপ্ট ইঞ্জিনে একটি লগিং সিস্টেম বিল্ট-ইন থাকে যা ব্যাকগ্রাউন্ডে কী ঘটছে তার একটি দুর্দান্ত ইনসাইট প্রদান করতে পারে।
Chrome একেবারেই রান করছে না, এই সমস্যা থেকে শুরু করে, ফ্ল্যাগ সহ Chrome লঞ্চ করা:
--no-sandbox --js-flags="--prof --noprof-lazy --log-timer-events"
এবং তারপর Chrome থেকে সম্পূর্ণভাবে বেরিয়ে গেলে বর্তমান ডিরেক্টরিতে একটি v8.log ফাইল তৈরি হবে।
v8.log-এর কন্টেন্ট ব্যাখ্যা করতে, আপনার Chrome যে v8 ভার্সন ব্যবহার করছে (about:version চেক করুন) আপনাকে সেই একই ভার্সন ডাউনলোড করতে হবে এবং এটি তৈরি করতে হবে।
v8 তৈরি করার পরে, আপনি টিক প্রসেসর ব্যবহার করে লগ প্রসেস করতে পারবেন:
$ tools/linux-tick-processor /path/to/v8.log
(আপনার প্ল্যাটফর্মের উপর নির্ভর করে linux-এর পরিবর্তে mac বা windows ব্যবহার করুন।) (এই টুলটি v8-এ টপ লেভেল সোর্স ডিরেক্টরি থেকে রান করতে হবে।)
টিক প্রসেসর জাভাস্ক্রিপ্ট ফাংশনের টেক্সট ভিত্তিক টেবিল দেখায়, যাতে সবচেয়ে বেশি টিক থাকে:
[JavaScript]:
ticks total nonlib name
167 61.2% 61.2% LazyCompile: *opt demo.js:12
40 14.7% 14.7% LazyCompile: unopt demo.js:20
15 5.5% 5.5% Stub: KeyedLoadElementStub
13 4.8% 4.8% Stub: BinaryOpStub_MUL_Alloc_Number+Smi
6 2.2% 2.2% Stub: BinaryOpStub_ADD_OverwriteRight_Number+Number
4 1.5% 1.5% Stub: KeyedStoreElementStub
4 1.5% 1.5% KeyedLoadIC: {12}
2 0.7% 0.7% KeyedStoreIC: {13}
1 0.4% 0.4% LazyCompile: ~main demo.js:30
আপনি দেখতে পাবেন যে demo.js-এ তিনটি ফাংশন আছে: opt, unopt এবং main। অপ্টিমাইজ করা ফাংশনের নামের পাশে একটি অ্যাস্টেরিস্ক (*) থাকে। লক্ষ্য করুন যে opt ফাংশনটি অপ্টিমাইজ করা এবং unopt ফাংশনটি অপ্টিমাইজ করা নয়।
V8 ডিটেকটিভের টুল ব্যাগের আরেকটি গুরুত্বপূর্ণ টুল হল plot-timer-event। এটি এইভাবে এক্সিকিউট করা যেতে পারে:
$ tools/plot-timer-event /path/to/v8.log
রান করার পরে, timer-events.png নামের একটি png ফাইল বর্তমান ডিরেক্টরিতে রয়েছে। এটি খুললে আপনি এমন কিছু দেখতে পাবেন:
নিচের দিকে গ্রাফ ছাড়াও, ডেটা সারিতে দেখানো হয়। X অক্ষ হল সময় (মিলিসেকেন্ড)। বাঁদিকে প্রতিটি সারির জন্য লেবেল থাকে:
V8.Execute সারিতে প্রতিটি প্রোফাইল টিকের উপর কালো উল্লম্ব রেখা আঁকা থাকে যেখানে V8 জাভাস্ক্রিপ্ট কোড এক্সিকিউট করছিল। V8.GCScavenger-এর প্রতিটি প্রোফাইল টিকের উপর একটি নীল উল্লম্ব রেখা আঁকা থাকে যেখানে V8 একটি নতুন জেনারেশন কালেকশন পারফর্ম করছিল। বাকি V8 স্টেটের ক্ষেত্রেও একই কথা প্রযোজ্য।
সবচেয়ে গুরুত্বপূর্ণ সারিগুলির মধ্যে একটি হল "এক্সিকিউট করা কোডের ধরন"। অপ্টিমাইজ করা কোড এক্সিকিউট করার সময় এটি সবুজ রঙের হবে এবং অপ্টিমাইজ না করা কোড এক্সিকিউট করার সময় লাল ও নীল রঙের মিশ্রণ হবে। নিচের স্ক্রিনশটে অপ্টিমাইজ করা থেকে অপ্টিমাইজ না করা এবং তারপর আবার অপ্টিমাইজ করা কোডে পরিবর্তন দেখানো হয়েছে:
সাধারণত, এই লাইনটি সলিড সবুজ রঙের হবে, তবে কখনই সাথে সাথে নয়। এর অর্থ হল, আপনার প্রোগ্রামটি একটি অপ্টিমাইজ করা স্থিতিশীল অবস্থায় পরিবর্তিত হয়েছে। অপ্টিমাইজ না করা কোড সবসময় অপ্টিমাইজ করা কোডের চেয়ে ধীরে রান করবে।
আপনি যদি এই পর্যন্ত এসে থাকেন, তাহলে এটি জেনে রাখা ভালো যে আপনার অ্যাপ্লিকেশন রিফ্যাক্টর করে আপনি আরও দ্রুত কাজ করতে পারবেন, যাতে এটি v8 ডিবাগ শেল: d8-এ রান করতে পারে। d8 ব্যবহার করলে আপনি টিক-প্রসেসর এবং প্লট-টাইমার-ইভেন্ট টুলের সাহায্যে আরও দ্রুত ইটারেশন টাইম পাবেন। d8 ব্যবহার করার আরেকটি সুবিধা হল, এর ফলে আসল সমস্যাটি আলাদা করা সহজ হয় এবং ডেটাতে উপস্থিত নয়েজের পরিমাণ কমে যায়।
Oz সোর্স কোড থেকে টাইমার ইভেন্ট প্লট দেখলে বোঝা যায় যে অপ্টিমাইজ করা কোড থেকে অপ্টিমাইজ না করা কোডে ট্রানজিশন হয়েছে এবং অপ্টিমাইজ না করা কোড এক্সিকিউট করার সময় অনেক নতুন জেনারেশন কালেকশন ট্রিগার করা হয়েছে, যা নিচের স্ক্রিনশটের মতো (মনে রাখবেন, মাঝখানে সময় সরিয়ে দেওয়া হয়েছে):
আপনি যদি ভাল করে দেখেন, তাহলে দেখতে পাবেন যে V8 কখন জাভাস্ক্রিপ্ট কোড এক্সিকিউট করছে তা নির্দেশকারী কালো লাইনগুলি ঠিক একই প্রোফাইল টিক টাইমে অনুপস্থিত, যেমনটি নতুন জেনারেশন কালেকশনের (নীল লাইন) ক্ষেত্রে দেখা যায়। এটি স্পষ্টভাবে দেখায় যে আবর্জনা সংগ্রহ করার সময় স্ক্রিপ্ট পজ করা আছে।
Oz সোর্স কোড থেকে টিক প্রসেসর আউটপুট দেখে বোঝা যাচ্ছে যে টপ ফাংশন (updateSprites) অপ্টিমাইজ করা হয়নি। অন্যভাবে বলতে গেলে, যে ফাংশনে প্রোগ্রামটি সবচেয়ে বেশি সময় ব্যয় করেছে সেটিও অপ্টিমাইজ করা হয়নি। এটি থেকে জোরালোভাবে ইঙ্গিত পাওয়া যায় যে সন্দেহভাজন #৩ হল অপরাধী। updateSprites-এর সোর্সে লুপ ছিল যা দেখতে এইরকম:
function updateSprites(dt) {
for (var sprite in sprites) {
sprite.position.x += 0.5 * dt;
// 20 more lines of arithmetic computation.
}
}
V8 সম্পর্কে তাদের গভীর জ্ঞান থাকার কারণে, তারা অবিলম্বে বুঝতে পেরেছিলেন যে for-i-in লুপ কনস্ট্রাক্ট কখনও কখনও V8 দ্বারা অপ্টিমাইজ করা হয় না। অন্যভাবে বলতে গেলে, কোনও ফাংশনে for-i-in লুপ কনস্ট্রাক্ট থাকলে, সেটি অপ্টিমাইজ করা নাও হতে পারে। এটি আজকের একটি বিশেষ কেস এবং ভবিষ্যতে পরিবর্তিত হতে পারে, অর্থাৎ, V8 একদিন এই লুপ কনস্ট্রাক্ট অপ্টিমাইজ করতে পারে। যেহেতু আমরা V8 ডিটেক্টিভ নই এবং V8 সম্পর্কে আমাদের খুব বেশি জ্ঞান নেই, তাই updateSprites কেন অপ্টিমাইজ করা হয়নি তা আমরা কীভাবে নির্ধারণ করব?
এক্সপেরিমেন্ট #২
এই ফ্ল্যাগ সহ Chrome রান করছে:
--js-flags="--trace-deopt --trace-opt-verbose"
অপ্টিমাইজেশন ও ডিঅপ্টিমাইজেশন ডেটার ভার্বোস লগ দেখায়। updateSprites-এর জন্য ডেটা সার্চ করে আমরা যা পেয়েছি:
[disabled optimization for updateSprites, reason: ForInStatement is not fast case]
ডিটেকটিভরা যেমন অনুমান করেছিলেন, for-i-in লুপ কনস্ট্রাক্টই ছিল এর কারণ।
কেস বন্ধ করা হয়েছে
updateSprites অপ্টিমাইজ না হওয়ার কারণ খুঁজে পাওয়ার পরে, সমাধানটি সহজ ছিল, শুধু কম্পিউটেশনটি তার নিজস্ব ফাংশনে সরান, অর্থাৎ:
function updateSprite(sprite, dt) {
sprite.position.x += 0.5 * dt;
// 20 more lines of arithmetic computation.
}
function updateSprites(dt) {
for (var sprite in sprites) {
updateSprite(sprite, dt);
}
}
updateSprite অপ্টিমাইজ করা হবে, যার ফলে অনেক কম HeapNumber অবজেক্ট তৈরি হবে এবং GC পজ কম ঘন ঘন হবে। নতুন কোড দিয়ে একই পরীক্ষা করে এটি কনফার্ম করা আপনার পক্ষে সহজ হবে। মনোযোগী পাঠক লক্ষ্য করবেন যে ডবল নম্বর এখনও প্রপার্টি হিসেবে স্টোর করা হচ্ছে। প্রোফাইলিং থেকে যদি বোঝা যায় যে এটি করা উচিত, তাহলে ডাবল বা টাইপ করা ডেটা অ্যারেতে পজিশন পরিবর্তন করলে তৈরি হওয়া অবজেক্টের সংখ্যা আরও কমে যাবে।
উপসংহার
Oz-এর ডেভেলপাররা এখানেই থেমে থাকেননি। V8 ডিটেকটিভদের থেকে পাওয়া টুল ও টেকনিক ব্যবহার করে তারা আরও কিছু ফাংশন খুঁজে পান যেগুলি ডিঅপ্টিমাইজেশনের নরকে আটকে ছিল এবং কম্পিউটেশন কোডকে লিফ ফাংশনে ফ্যাক্টর করেন যেগুলি অপ্টিমাইজ করা ছিল, যার ফলে আরও ভালো পারফর্ম্যান্স পাওয়া যায়।
বাইরে বেরিয়ে আসুন এবং পারফর্ম্যান্স সংক্রান্ত অপরাধের সমাধান করা শুরু করুন!