Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

आरएसपीईसी के साथ ऑब्जेक्ट आवंटन का परीक्षण

हर कोई रूबी के प्रदर्शन के बारे में हाल ही में और अच्छे कारणों से बात कर रहा है। यह पता चला है कि आपके कोड में कुछ छोटे बदलावों के साथ प्रदर्शन को 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/

मैं अभी भी प्रदर्शन के लिए रूबी कोड की रूपरेखा तैयार करने के अपने दृष्टिकोण को परिष्कृत कर रहा हूं; मुझे उम्मीद है कि एक प्रदर्शन परीक्षण सूट बनाए रखने से मुझे अपने कोड की गति को अभी बेहतर बनाने में मदद मिलेगी, भविष्य में इसे तेज बनाए रखने और अपने और दूसरों के लिए दस्तावेज़ बनाने में मदद मिलेगी।


  1. सी # कोड के लिए यूनिट परीक्षण

    यूनिट परीक्षण C# कोड की कुंजी है क्योंकि यह विकास प्रक्रिया में कोड को बनाए रखने में मदद कर सकता है। यह आपको विकास चक्र में आने वाली समस्याओं के बारे में बताता है। यूनिट परीक्षण के साथ, आप कोड को विश्वसनीय और पुन:प्रयोज्य बना सकते हैं। यूनिट टेस्टिंग को अपनाने के मूलभूत सिद्धांतों में से एक टीडीडी

  1. QRGen के साथ दुर्भावनापूर्ण क्यूआर कोड

    क्यूआर कोड मशीन-पठनीय डेटा प्रारूप हैं जिनका उपयोग किसी भी चीज़ के लिए किया जाता है जिसे स्वचालित रूप से स्कैन करने की आवश्यकता होती है। कस्टम क्यूआर कोड में पैक किए गए कारनामों का उपयोग करके सामान्य कमजोरियों का फायदा उठाना संभव है क्योंकि यह उत्पाद पैकेजिंग से लेकर एयरलाइन बोर्डिंग पास आदि तक हर ज

  1. रुबोकॉप के साथ लाइनिंग और ऑटो-फॉर्मेटिंग रूबी कोड

    लाइनिंग प्रोग्रामेटिक और शैलीगत त्रुटियों के लिए स्रोत कोड की स्वचालित जाँच है। यह जाँच एक स्थिर कोड विश्लेषण उपकरण द्वारा की जाती है जिसे लिंटर कहा जाता है। एक कोड फ़ॉर्मेटर, हालांकि, स्रोत कोड को स्वरूपित करने से संबंधित एक उपकरण है, ताकि यह नियमों के पूर्व-कॉन्फ़िगर किए गए सेट का सख्ती से पालन कर