معیار IT: تصمیمات بهینه سازی بر اساس داده های واقعی و نه لرزش

عملکرد کد ما چیزی است که همه ما باید به عنوان توسعه دهندگان به آن اهمیت دهیم. ما همیشه باید در تلاش باشیم تا کد خود را به همان سرعت و به همان اندازه کارآمد انجام دهیم. اما این سؤال را ایجاد می کند ، چگونه می توانیم به طور عینی کد خود را اندازه گیری کنیم؟
به جای اینکه حدس بزنیم که چه عملی را صرفاً بر اساس لرزش در نظر می گیریم ، معیار می تواند داده های تجربی را به ما بدهد تا بتوانیم تصمیمات خوبی بگیریم. به طور خاص ، ما می خواهیم این کار را انجام دهیم که مسیرهای کد را بهینه می کنیم ، رویکردهای مختلف را مقایسه می کنیم و کتابخانه های شخص ثالث را ارزیابی می کنیم.
ما نمی توانیم به غرایز و لرزش خود اعتماد کنیم تا مشخص کنیم کد ما چقدر خوب است ، ما به داده نیاز داریم. روبی یک کتابخانه برای آن دارد.
شروع
ما قصد داریم از کتابخانه معیار استفاده کنیم که بخشی از کتابخانه استاندارد روبی است. ما با نیاز به آن شروع می کنیمبشر
require 'benchmark'
اولین کاری که ما می خواهیم انجام دهیم این است که اندازه گیری کنید که چه مدت طول می کشد تا مجموعه ای از یک میلیون عنصر ایجاد شود.
require 'benchmark'
time = Benchmark.measure do
array = Array.new(1_000_000) do |i|
i
end
end
puts time
ما خروجی دریافت خواهیم کرد که به نظر می رسد:
0.024869 0.000951 0.025820 ( 0.025847)
این اعداد از راست به چپ نشان می دهد:
- زمان پردازنده کاربر
- زمان پردازنده سیستم
- کل زمان پردازنده (زمان پردازنده کاربر + زمان پردازنده سیستم)
- زمان سپری شده واقعی – این در پرانتز است.
زمان سپری شده واقعی همان چیزی است که ما به آن علاقه مند هستیم زیرا این چیزی را که کاربران تجربه می کنند نشان می دهد. زمان CPU کاربر زمان صرف شده توسط رایانه است که کد ما را در حالت کاربر اجرا می کند ، یعنی کد یاقوت که ما نوشتیم ، چیزهای کتابخانه استاندارد روبی ، کد در هر سنگهای قیمتی که ما استفاده می کنیم و مترجم روبی است. زمان پردازنده سیستم موارد پایین تر از سطح پایین مانند همه کارهایی است که سیستم عامل ما مطابق درخواست Ruby ما انجام می دهد ، مانند عملیات فایل ، تخصیص حافظه و مدیریت و غیره.
من زیباتر می خواهم!
مسلماً ، این خروجی به راحتی قابل خواندن نیست. بنابراین ما می توانیم چیزها را به سطح بعدی برسانیم. ما می توانیم از Benchmark.bm برای ایجاد نتیجه بسیار خواندنی استفاده کنیم و به ما اجازه می دهیم رویکردهای الگوریتمی را در برابر یکدیگر مقایسه کنیم.
این کد همان کار را انجام می دهد – مجموعه ای از یک میلیون عنصر را ایجاد کنید ، اما این کار را با استفاده از سه رویکرد مختلف انجام می دهد و سپس گزارشی را به ما می دهد که به ما امکان می دهد تا تعیین کنیم که کدام روش بهترین است. می خواهم توجه داشته باشم که 10 مورد به روش #BM منتقل می شود ، عرض هر ستون را مشخص می کند. شما می توانید آن را بر روی هر چیز دیگری تنظیم کنید ، ما فقط از 10 استفاده می کنیم زیرا این در اینجا متناسب با اهداف ما است.
require 'benchmark'
n = 1_000_000
Benchmark.bm(10) do |x|
x.report("Array.new:") { Array.new(n) { |i| i } }
x.report("Range:") { (0...n).to_a }
x.report("upto:") {
array = []
0.upto(n-1) { |i| array << i }
array
}
end
در حال اجرا که ما خروجی مشابه این را دریافت می کنیم:
user system total real
Array.new: 0.024696 0.000604 0.025300 ( 0.025357)
Range: 0.011129 0.001368 0.012497 ( 0.012498)
upto: 0.027432 0.001566 0.028998 ( 0.029064)
فقط با نگاهی به این امر ، می توانیم بگوییم که استفاده از رویکرد دامنه سریعترین است.
آن را گرم کنید
نکته مهمی که باید از آن آگاه باشیم این است که وقتی در روبی معیار می شویم ، گاهی اوقات ، اولین اجرای کمی از کد متفاوت از اجراهای بعدی عمل می کند. برای پرداختن به این موضوع ، ما روش #BMBM بسیار خلاقانه ای داریم. این کار این است که کد شما را دو بار اجرا می کند. اولین بار به منظور گرم کردن کارها و بار دوم این کار را برای واقعیت ها انجام می دهد. این داده ها را برای هر دو فراهم می کند تا بتوانید تعیین کنید که آیا نوعی تأثیر وجود داشته است یا خیر.
بیایید به یک مثال نگاه کنیم. ما قصد داریم یک آرایه بزرگ ایجاد کنیم و مواردی را برای آن انجام دهیم:
require 'benchmark'
array = (1..1_000_000).to_a
sorted = array.dup
reversed = array.reverse
Benchmark.bmbm(7) do |x|
x.report("sort:") { array.dup.sort }
x.report("sort!:") { sorted.dup.sort! }
x.report("reverse:") { reversed.dup.sort }
end
با اجرای این کار ، ما این نتیجه را می گیریم:
Rehearsal --------------------------------------------
sort: 0.005534 0.001237 0.006771 ( 0.006823)
sort!: 0.005565 0.001304 0.006869 ( 0.006881)
reverse: 0.027210 0.001318 0.028528 ( 0.028594)
----------------------------------- total: 0.042168sec
user system total real
sort: 0.005511 0.001054 0.006565 ( 0.006577)
sort!: 0.005554 0.001061 0.006615 ( 0.006617)
reverse: 0.026982 0.001266 0.028248 ( 0.028336)
کد به شما می گوید که تمرین چه مدت طول کشید. می توانیم ببینیم که همه چیز فقط کمی سریعتر اجرا می شود.
اما آیا این مقیاس است؟
ما می توانیم از این کتابخانه برای ایجاد گزارشی استفاده کنیم تا در صورت اندازه گیری کد ما به ما نشان دهد.
این کمی از کد کدی را که ما چهار بار با اندازه های مختلف آزمایش می کنیم ، اجرا می کند تا ببیند که چگونه بر اساس میزان داده هایی که باید با آن سروکار داشته باشد ، عملکرد آن را انجام می دهد.
require 'benchmark'
sizes = [50_000, 100_000, 500_000, 1_000_000]
Benchmark.benchmark(Benchmark::CAPTION, 15, Benchmark::FORMAT, ">total:", ">avg:") do |x|
results = sizes.map do |size|
x.report("Array(#{size}):") do
Array.new(size) { |i| i }
end
end
sum = results.inject(:+)
[sum, sum / results.size]
end
این نتایج حاصل از آن است:
user system total real
Array(50000): 0.001398 0.000038 0.001436 ( 0.001462)
Array(100000): 0.002823 0.000114 0.002937 ( 0.002938)
Array(500000): 0.014158 0.000335 0.014493 ( 0.014605)
Array(1000000): 0.028183 0.000675 0.028858 ( 0.028969)
>total: 0.046562 0.001162 0.047724 ( 0.047974)
>avg: 0.011640 0.000291 0.011931 ( 0.011994)
پایان
شما باید کد خود را معیار کنید ، و گرچه ابزارهای دیگری برای آن وجود دارد ، روبی یکی از آنها را در کتابخانه استاندارد خود به شما می دهد. با این دانش می توانیم اطمینان حاصل کنیم که ما در حال تصمیم گیری بهینه سازی بر اساس داده های واقعی هستیم و نه لرزش و ما می توانیم پیامدهای عملکرد رویکردهای مختلف الگوریتمی را درک کنیم.
با این حال ، به یاد داشته باشید که عملکرد تنها یک بعد کد خوب است. خوانایی و حفظ قابلیت اطمینان به طور کلی بسیار مهمتر است. سریعترین کد همیشه بهترین انتخاب نیست.
این وبلاگ توسط مربی تورینگ ، مایک دائو نوشته شده است.
حتماً ما را در اینستاگرام ، X و LinkedIn -turing_school دنبال کنید