हर कोई रूबी के प्रदर्शन के बारे में हाल ही में और अच्छे कारणों से बात कर रहा है। यह पता चला है कि आपके कोड में कुछ छोटे बदलावों के साथ प्रदर्शन को 99.9% तक बढ़ाना संभव है।
कैसे . पर बहुत सारे लेख उपलब्ध हैं अपने कोड को अनुकूलित करने के लिए, लेकिन आप यह कैसे सुनिश्चित कर सकते हैं कि आपका कोड बना रहता है अनुकूलित?
नियमित रूप से बुलाए जाने वाले तरीके में फ्रोजन स्थिरांक के बजाय स्ट्रिंग अक्षर को एम्बेड करते समय आप हमेशा परिणामों पर विचार नहीं कर सकते हैं - भविष्य में अपना कोड बनाए रखते हुए अपने अनुकूलन की बचत खोना बहुत आसान है।
हाल ही में ये मेरे विचार थे क्योंकि मैंने हनीबैजर में हमारे रूबी रत्न में दूसरी (या तीसरी) बार कुछ कोड अनुकूलित किए थे:"क्या यह बहुत अच्छा नहीं होगा यदि यह सुनिश्चित करने का कोई तरीका है कि ये अनुकूलन पुनर्प्राप्त नहीं करते हैं /em> ?"
प्रतिगमन कुछ ऐसी चीजें हैं जिनसे हम में से अधिकांश सॉफ्टवेयर विकास से परिचित हैं, भले ही नाम से नहीं। एक प्रतिगमन तब होता है जब एक बग या एक समस्या जिसे अतीत में हल किया गया था, उसी कोड में भविष्य में परिवर्तन के कारण फिर से होता है। कोई भी एक ही काम को एक से अधिक बार करना पसंद नहीं करता है; प्रतिगमन फर्श पर गंदगी को साफ करने के ठीक बाद ट्रैक करने जैसा है।
सौभाग्य से, हमारे पास एक गुप्त हथियार है:परीक्षण। आप हठधर्मिता का अभ्यास करते हैं या नहीं, परीक्षण अद्भुत हैं बग को ठीक करने के लिए क्योंकि वे समस्या और समाधान को प्रोग्रामेटिक रूप से प्रदर्शित करते हैं। परीक्षण हमें विश्वास दिलाते हैं कि परिवर्तन होने पर प्रतिगमन नहीं होगा।
जाना पहचाना? मैंने भी ऐसा सोचा था, जिसने मुझे आश्चर्यचकित कर दिया, "यदि प्रदर्शन अनुकूलन वापस आ सकते हैं, तो मैं उन प्रतिगमनों को परीक्षणों के साथ क्यों नहीं पकड़ सकता?"
रूबी के विभिन्न प्रदर्शन पहलुओं की रूपरेखा बनाने के लिए बहुत सारे बेहतरीन टूल हैं जिनमें ऑब्जेक्ट आवंटन, मेमोरी, सीपीयू, कचरा संग्रह इत्यादि शामिल हैं। इनमें से कुछ में रूबी-प्रोफ, स्टैकप्रोफ और आवंटन_ट्रैसर शामिल हैं।
मैं हाल ही में ऑब्जेक्ट आवंटन को प्रोफाइल करने के लिए आवंटन_स्टैट्स का उपयोग कर रहा हूं। आवंटन को कम करना काफी आसान काम है, स्मृति खपत और गति को ट्यून करने के लिए बहुत सारे कम लटकने वाले फल उत्पन्न करना।
उदाहरण के लिए, यहां एक मूल रूबी वर्ग है जो 5 स्ट्रिंग्स का एक ऐरे संग्रहीत करता है जो डिफ़ॉल्ट रूप से 'foo':
class MyClass
def initialize
@values = Array.new(5)
5.times { @values << 'foo' }
end
end
एलोकेशनस्टैट्स एपीआई सरल है। इसे प्रोफ़ाइल के लिए एक ब्लॉक दें, और यह प्रिंट करेगा कि सबसे अधिक ऑब्जेक्ट कहां आवंटित किए गए हैं।
$ ruby -r allocation_stats -r ./lib/my_class
stats = AllocationStats.trace { MyClass.new }
puts stats.allocations(alias_paths: true).group_by(:sourcefile, :sourceline, :class).to_text
^D
sourcefile sourceline class count
--------------------- ---------- ------- -----
/lib/my_class.rb 4 String 5
/lib/my_class.rb 3 Array 1
- 1 MyClass 1
#to_text
विधि (आवंटन के समूह पर बुलाया जाता है) बस एक अच्छी मानव-पठनीय तालिका को प्रिंट करता है जिसे आप जो भी मानदंड मांगते हैं उसके आधार पर समूहित किया जाता है।
मैन्युअल रूप से प्रोफाइलिंग करते समय यह आउटपुट बहुत अच्छा है, लेकिन मेरा लक्ष्य एक परीक्षण बनाना था जो मेरे सामान्य यूनिट टेस्ट सूट (जो आरएसपीसी में लिखा गया है) के साथ चल सकता है। हम देख सकते हैं कि my_class.rb की लाइन 4 पर, 5 स्ट्रिंग आवंटित की जा रही हैं , जो अनावश्यक लगता है क्योंकि मुझे पता है कि उन सभी में एक ही मूल्य है। मैं चाहता था कि मेरा परिदृश्य कुछ इस तरह पढ़े:"MyClass को प्रारंभ करते समय यह 6 ऑब्जेक्ट्स के तहत आवंटित करता है"। RSpec में यह कुछ इस तरह दिखता है:
describe MyClass do
context "when initializing" do
specify { expect { MyClass.new }.to allocate_under(6).objects }
end
end
इस सिंटैक्स का उपयोग करते हुए मेरे पास वह सब कुछ है जो मुझे परीक्षण करने की आवश्यकता है कि ऑब्जेक्ट आवंटन कोड के वर्णित ब्लॉक के लिए दी गई संख्या से कम है (expect
के अंदर) ब्लॉक) एक कस्टम RSpec मिलानकर्ता का उपयोग करके।
ट्रेस परिणामों को प्रिंट करने के अलावा, एलोकेशनस्टैट्स रूबी के माध्यम से आवंटन तक पहुंचने के लिए कुछ तरीके प्रदान करता है, जिसमें #allocations
शामिल है। और #new_allocations
. मैं अपना मैचर बनाने के लिए इन चीज़ों का इस्तेमाल करता था:
begin
require 'allocation_stats'
rescue LoadError
puts 'Skipping AllocationStats.'
end
RSpec::Matchers.define :allocate_under do |expected|
match do |actual|
return skip('AllocationStats is not available: skipping.') unless defined?(AllocationStats)
@trace = actual.is_a?(Proc) ? AllocationStats.trace(&actual) : actual
@trace.new_allocations.size < expected
end
def objects
self
end
def supports_block_expectations?
true
end
def output_trace_info(trace)
trace.allocations(alias_paths: true).group_by(:sourcefile, :sourceline, :class).to_text
end
failure_message do |actual|
"expected under #{ expected } objects to be allocated; got #{ @trace.new_allocations.size }:\n\n" << output_trace_info(@trace)
end
description do
"allocates under #{ expected } objects"
end
end
मैं बचा रहा हूँ LoadError
प्रारंभिक आवश्यकता कथन में क्योंकि मैं प्रत्येक टेस्ट रन पर आवंटनस्टैट्स को शामिल नहीं करना चाहता (यह परीक्षणों को धीमा कर देता है)। फिर मैं :allocate_under
. को परिभाषित करता हूं मैचर जो match
. के अंदर ट्रेस करता है खंड मैथा। failure_message
ब्लॉक इसलिए भी महत्वपूर्ण है क्योंकि इसमें to_text
. शामिल है एलोकेशनस्टैट्स ट्रेस से आउटपुट मेरे विफलता संदेश के ठीक अंदर ! शेष मिलानकर्ता अधिकतर मानक RSpec कॉन्फ़िगरेशन है।
मेरे मैचर लोड होने के साथ, मैं अब अपने परिदृश्य को पहले से चला सकता हूं, और इसे विफल देख सकता हूं:
$ rspec spec/my_class_spec.rb
MyClass
when initializing
should allocates under 6 objects (FAILED - 1)
Failures:
1) MyClass when initializing should allocates under 6 objects
Failure/Error: expect { MyClass.new }.to allocate_under(6).objects
expected under 6 objects to be allocated; got 7:
sourcefile sourceline class count
--------------------------- ---------- ------- -----
<PWD>/spec/my_class_spec.rb 6 MyClass 1
<PWD>/lib/my_class.rb 3 Array 1
<PWD>/lib/my_class.rb 4 String 5
# ./spec/my_class_spec.rb:6:in `block (3 levels) in <top (required)>'
Finished in 0.15352 seconds (files took 0.22293 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/my_class_spec.rb:5 # MyClass when initializing should allocates under 6 objects
ठीक है, इसलिए मैंने प्रोग्रामेटिक रूप से प्रदर्शन समस्या का प्रदर्शन किया है, जो कि MyClass समान मान के साथ अतिरिक्त स्ट्रिंग ऑब्जेक्ट आवंटित करता है। आइए उन मानों को फ़्रीज़ किए गए स्थिरांक में डालकर उस समस्या को ठीक करें:
class MyClass
DEFAULT = 'foo'.freeze
def initialize
@values = Array.new(5)
5.times { @values << DEFAULT }
end
end
अब जबकि मैंने समस्या को ठीक कर लिया है, मैं अपना परीक्षण फिर से चलाऊंगा और इसे पास होते हुए देखूंगा:
$ rspec spec/my_class_spec.rb
MyClass
when initializing
should allocates under 6 objects
Finished in 0.14952 seconds (files took 0.22056 seconds to load)
1 example, 0 failures
अगली बार मैं MyClass#initialize
. को बदलूंगा विधि, मुझे विश्वास हो सकता है कि मैं बहुत अधिक वस्तुओं को आवंटित नहीं कर रहा हूँ।
चूंकि प्रोफाइलिंग आवंटन अपेक्षाकृत धीमा हो सकता है, इसलिए इन ऑन-डिमांड को हर समय चलाने के बजाय आदर्श होगा। क्योंकि मैं पहले से ही एलोकेशन_स्टैट्स के गायब होने को इनायत से संभाल रहा हूं, मैं बंडलर का उपयोग कई जेमफाइल बनाने के लिए कर सकता हूं और फिर निर्दिष्ट कर सकता हूं कि मैं किस जेमफाइल का उपयोग करना चाहता हूं BUNDLE_GEMFILE पर्यावरण चर:
$ BUNDLE_GEMFILE=with_performance.gemfile bundle exec rspec spec/
$ BUNDLE_GEMFILE=without_performance.gemfile bundle exec rspec spec/
एक अन्य विकल्प मूल्यांकन रत्न जैसी लाइब्रेरी का उपयोग करना है, जो इसी दृष्टिकोण को अपनाता है और कुछ बंडलर गॉचा को हल करता है। जेसन क्लार्क ने मार्च 2015 में रूबी ऑन एलेस में इसे कैसे करना है, इस पर एक उत्कृष्ट प्रस्तुति दी; अधिक जानने के लिए उनकी स्लाइड देखें।
मुझे यह भी लगता है कि मेरे सामान्य इकाई परीक्षणों से अलग इस प्रकार के परीक्षणों को बनाए रखना एक अच्छा विचार है, इसलिए मैं एक नई "प्रदर्शन" निर्देशिका बनाउंगा ताकि मेरा यूनिट टेस्ट सूट स्पेक/यूनिट/में रहता है और मेरा प्रदर्शन सूट स्पेक में रहता है /प्रदर्शन/:
spec/
|-- spec_helper.rb
|-- unit/
|-- features/
|-- performance/
मैं अभी भी प्रदर्शन के लिए रूबी कोड की रूपरेखा तैयार करने के अपने दृष्टिकोण को परिष्कृत कर रहा हूं; मुझे उम्मीद है कि एक प्रदर्शन परीक्षण सूट बनाए रखने से मुझे अपने कोड की गति को अभी बेहतर बनाने में मदद मिलेगी, भविष्य में इसे तेज बनाए रखने और अपने और दूसरों के लिए दस्तावेज़ बनाने में मदद मिलेगी।