Erlang 21 - 4. Constructing and Matching Binaries

4 बायनेरिज़ का निर्माण और मिलान




erlang

4 बायनेरिज़ का निर्माण और मिलान

बायनेरिज़ को निम्नलिखित तरीके से कुशलतापूर्वक बनाया जा सकता है:

करना

my_list_to_binary(List) ->
    my_list_to_binary(List, <<>>).

my_list_to_binary([H|T], Acc) ->
    my_list_to_binary(T, <<Acc/binary,H>>);
my_list_to_binary([], Acc) ->
    Acc.

बायनेरिज़ का कुशलता से इस तरह मिलान किया जा सकता है:

करना

my_binary_to_list(<<H,T/binary>>) ->
    [H|my_binary_to_list(T)];
my_binary_to_list(<<>>) -> [].

4.1 बायनेरिज़ को कैसे लागू किया जाता है

आंतरिक रूप से, बायनेरिज़ और बिटस्ट्रिंग्स को उसी तरह लागू किया जाता है। इस खंड में, उन्हें बायनेरी कहा जाता है, क्योंकि उन्हें एमुलेटर स्रोत कोड में कहा जाता है।

चार प्रकार की बाइनरी ऑब्जेक्ट आंतरिक रूप से उपलब्ध हैं:

  • दो द्विआधारी डेटा के लिए कंटेनर हैं और कहा जाता है:

    • Refc बायनेरिज़ ( संदर्भ-गणना बायनेरिज़ के लिए छोटा)
    • हीप बायनेरिज़
  • दो केवल एक बाइनरी के एक हिस्से के संदर्भ हैं और कहा जाता है:

    • उप बायनेरिज़
    • संदर्भों का मिलान करें

रेफरी बायनेरिज़

Refc बायनेरिज़ में दो भाग होते हैं:

  • प्रक्रिया ढेर पर संग्रहीत एक वस्तु, जिसे ProcBin कहा जाता है
  • बाइनरी ऑब्जेक्ट ही, सभी प्रक्रिया के बाहर संग्रहीत है

किसी भी संख्या की प्रक्रियाओं से किसी भी प्रोसीबींस द्वारा बाइनरी ऑब्जेक्ट को संदर्भित किया जा सकता है। ऑब्जेक्ट में संदर्भ की संख्या का ट्रैक रखने के लिए एक संदर्भ काउंटर होता है, ताकि अंतिम संदर्भ के गायब होने पर इसे हटाया जा सके।

एक प्रक्रिया में सभी ProcBin ऑब्जेक्ट एक लिंक की गई सूची का हिस्सा हैं, ताकि कचरा संग्रहकर्ता उन पर नज़र रख सके और जब ProcBin गायब हो जाए तो बाइनरी में संदर्भ काउंटरों को घटा दें।

हीप बायनेरिज़

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

सब बायनेरिज़

संदर्भ ऑब्जेक्ट उप बायनेरी और मैच संदर्भ एक रिफाइनरी बाइनरी या हीप बाइनरी के हिस्से को संदर्भित कर सकते हैं।

एक उप बाइनरी को split_binary/2 द्वारा बनाया जाता है और जब एक बाइनरी को बाइनरी पैटर्न में मिलान किया जाता है। एक उप बाइनरी एक अन्य बाइनरी (रिफक या हीप बाइनरी, लेकिन कभी भी अन्य उप बाइनरी में) के हिस्से में एक संदर्भ है। इसलिए, बाइनरी का मिलान करना अपेक्षाकृत सस्ता है क्योंकि वास्तविक बाइनरी डेटा को कभी भी कॉपी नहीं किया जाता है।

मैच प्रसंग

एक मैच का संदर्भ उप बाइनरी के समान है, लेकिन बाइनरी मिलान के लिए अनुकूलित है। उदाहरण के लिए, इसमें बाइनरी डेटा का एक सीधा सूचक शामिल है। प्रत्येक क्षेत्र के लिए जो एक बाइनरी से मेल खाता है, मैच के संदर्भ में स्थिति बढ़ जाती है।

संकलक कोड उत्पन्न करने से बचने की कोशिश करता है जो एक उप बाइनरी बनाता है, केवल कुछ ही समय बाद एक नया मैच संदर्भ बनाता है और उप बाइनरी को त्याग देता है। उप बाइनरी बनाने के बजाय, मैच का संदर्भ रखा जाता है।

कंपाइलर केवल यह अनुकूलन कर सकता है यदि यह जानता है कि मैच का संदर्भ साझा नहीं किया जाएगा। यदि इसे साझा किया जाता, तो एर्लांग के कार्यात्मक गुण (जिसे संदर्भात्मक पारदर्शिता भी कहा जाता है) टूट जाता।

4.2 बायनेरिज़ का निर्माण

बाइनरी या बिटस्ट्रिंग में अप्लाई करना विशेष रूप से रनटाइम सिस्टम द्वारा अनुकूलित है:

<<Binary/binary, ...>>
<<Binary/bitstring, ...>>

जैसा कि रनटाइम सिस्टम ऑप्टिमाइज़ेशन (कंपाइलर के बजाय) को हैंडल करता है, बहुत कम ही परिस्थितियाँ ऐसी होती हैं जिनमें ऑप्टिमाइज़ेशन काम नहीं करता है।

यह कैसे काम करता है, यह समझाने के लिए, हम लाइन द्वारा निम्नलिखित कोड लाइन की जांच करते हैं:

Bin0 = <<0>>,                    %% 1
Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
{Bin4,Bin3}                      %% 6
  • पंक्ति 1 ( %% 1 टिप्पणी के साथ चिह्नित), Bin0 चर के लिए एक heap binary प्रदान करता है।
  • लाइन 2 एक परिशिष्ट ऑपरेशन है। के रूप में Bin0 एक परिशिष्ट ऑपरेशन में शामिल नहीं किया गया है, एक नया refc binary बनाया गया है और Bin0 की सामग्री को Bin0 कॉपी किया गया है। रिफाइनरी बाइनरी के प्रोकबिन भाग का आकार बाइनरी में संग्रहीत डेटा के आकार पर सेट होता है, जबकि बाइनरी ऑब्जेक्ट में अतिरिक्त स्थान आवंटित होता है। बाइनरी ऑब्जेक्ट का आकार, Bin1 या 256 के आकार से Bin1 , जो भी बड़ा है। इस मामले में यह 256 है।
  • पंक्ति 3 अधिक रोचक है। Bin1 का उपयोग एक परिशिष्ट ऑपरेशन में किया गया है, और इसमें अंत में अप्रयुक्त भंडारण के 252 बाइट्स हैं, इसलिए 3 नई बाइट्स वहां संग्रहीत हैं।
  • पंक्ति 4. वही यहाँ लागू होता है। 249 बाइट्स बचे हैं, इसलिए 3 बाइट्स को स्टोर करने में कोई समस्या नहीं है।
  • पंक्ति 5. यहाँ, कुछ दिलचस्प होता है। ध्यान दें कि परिणाम Bin3 में पिछले परिणाम के लिए संलग्न नहीं है, लेकिन Bin1 । यह उम्मीद की जाती है कि Bin4 को मान निर्दिष्ट किया जाएगा <<0,1,2,3,17>> । यह भी उम्मीद है कि Bin3 अपने मूल्य को बरकरार रखेगा ( <<0,1,2,3,4,5,6,7,8,9>> )। स्पष्ट रूप से, रनटाइम सिस्टम बाइनरी 17 को बाइनरी में नहीं लिख सकता है, क्योंकि यह Bin3 के मान को <<0,1,2,3,4,17,6,7,8,9>>

रनटाइम सिस्टम देखता है कि Bin1 पिछले परिशिष्ट ऑपरेशन (नवीनतम परिशिष्ट ऑपरेशन से नहीं) से परिणाम है, इसलिए यह Bin1 की सामग्री को एक नए बाइनरी, आरक्षित अतिरिक्त भंडारण, और इसी तरह से कॉपी करता है। (यहां यह नहीं बताया गया है कि रनटाइम सिस्टम कैसे जान सकता है कि उसे Bin1 में लिखने की अनुमति नहीं है; यह जानने के लिए जिज्ञासु पाठक को एक अभ्यास के रूप में छोड़ दिया जाता है कि यह एमुलेटर स्रोतों को पढ़ने के द्वारा कैसे किया जाता है, मुख्य रूप से erl_bits.c

परिस्थितियाँ जो बल नकल करती हैं

बाइनरी एपेंड ऑपरेशन के ऑप्टिमाइज़ेशन के लिए आवश्यक है कि बाइनरी के लिए एक प्रोकबिन और प्रोकबिन के लिए एक ही संदर्भ हो। कारण यह है कि बाइनरी ऑब्जेक्ट को एक परिशिष्ट ऑपरेशन के दौरान स्थानांतरित (वास्तविक) किया जा सकता है, और जब ऐसा होता है, तो प्रोबिन में सूचक को अपडेट किया जाना चाहिए। यदि बाइनरी ऑब्जेक्ट की ओर इशारा करते हुए एक से अधिक ProcBin होंगे, तो उन सभी को ढूंढना और अपडेट करना संभव नहीं होगा।

इसलिए, बाइनरी पर कुछ संचालन इसे चिह्नित करते हैं ताकि भविष्य के किसी भी अपेंडिक्स ऑपरेशन को द्विआधारी की नकल करने के लिए मजबूर किया जाएगा। ज्यादातर मामलों में, बाइनरी ऑब्जेक्ट को एक ही समय में सिकुड़ जाएगा ताकि बढ़ते हुए अतिरिक्त स्थान को पुनः प्राप्त किया जा सके।

जब द्विआधारी के रूप में निम्नानुसार लागू होता है, तो केवल नवीनतम परिशिष्ट ऑपरेशन से लौटा हुआ बाइनरी आगे के सस्ते परिशिष्ट कार्यों का समर्थन करेगा:

Bin = <<Bin0,...>>

इस खंड की शुरुआत में कोड के टुकड़े में, Bin को Bin0 करना सस्ता होगा, जबकि Bin0 लिए Bin0 करना एक नए द्विआधारी के निर्माण और Bin0 की सामग्री की नकल करने के लिए Bin0

यदि किसी बाइनरी को किसी प्रक्रिया या पोर्ट पर संदेश के रूप में भेजा जाता है, तो बाइनरी सिकुड़ जाएगी और आगे कोई भी अपेंडेशन ऑपरेशन बाइनरी डेटा को नए बाइनरी में कॉपी कर देगा। उदाहरण के लिए, निम्नलिखित कोड खंड में Bin1 को तीसरी पंक्ति में कॉपी किया जाएगा:

Bin1 = <<Bin0,...>>,
PortOrPid ! Bin1,
Bin = <<Bin1,...>>  %% Bin1 will be COPIED

यदि आप बाइनरी को Ets तालिका में सम्मिलित करते हैं, तो erlang:port_command/2 का उपयोग करके इसे पोर्ट पर भेजें erlang:port_command/2 , या इसे enif_inspect_binary में enif_inspect_binary को enif_inspect_binary

एक बाइनरी का मिलान करने से यह सिकुड़ जाएगा और अगला परिशिष्ट ऑपरेशन बाइनरी डेटा को कॉपी करेगा:

Bin1 = <<Bin0,...>>,
<<X,Y,Z,T/binary>> = Bin1,
Bin = <<Bin1,...>>  %% Bin1 will be COPIED

कारण यह है कि एक match context में बाइनरी डेटा के लिए एक सीधा सूचक होता है।

यदि कोई प्रक्रिया केवल बायनेरिज़ ("लूप डेटा" या प्रक्रिया शब्दकोश में) रखती है, तो कचरा कलेक्टर अंततः बायनेरिज़ को सिकोड़ सकता है। यदि केवल एक ही बाइनरी रखी जाती है, तो यह सिकुड़ा नहीं जाएगा। यदि बाद में प्रक्रिया एक बाइनरी के साथ जुड़ जाती है जो सिकुड़ गई है, तो डेटा को संलग्न करने के लिए जगह बनाने के लिए द्विआधारी वस्तु को पुनः प्राप्त किया जाएगा।

4.3 मैचिंग बायनेरी

आइए हम पिछले खंड की शुरुआत में उदाहरण को फिर से देखें:

करना

my_binary_to_list(<<H,T/binary>>) ->
    [H|my_binary_to_list(T)];
my_binary_to_list(<<>>) -> [].

पहली बार my_binary_to_list/1 कहा जाता है, एक match context बनाया जाता है। मैच संदर्भ बाइनरी के पहले बाइट को इंगित करता है। 1 बाइट का मिलान किया जाता है और बाइनरी में दूसरी बाइट को इंगित करने के लिए मैच का संदर्भ अपडेट किया जाता है।

इस बिंदु पर यह एक sub binary बनाने के लिए समझ में आता है, लेकिन इस विशेष उदाहरण में कंपाइलर देखता है कि जल्द ही एक फ़ंक्शन को कॉल किया जाएगा (इस मामले में, my_binary_to_list/1 ही) जो तुरंत एक नया वीडियो संदर्भ बनाएगा और उप बाइनरी त्यागें।

इसलिए my_binary_to_list/1 उप बाइनरी के बजाय मैच संदर्भ के साथ खुद को कॉल करता है। निर्देश जो मिलान ऑपरेशन को आरंभ करता है, मूल रूप से कुछ भी नहीं करता है जब यह देखता है कि यह बाइनरी के बजाय एक मैच संदर्भ पारित किया गया था।

जब बाइनरी का अंत हो जाता है और दूसरा क्लॉज मैच करता है, तो मैच का संदर्भ केवल त्याग दिया जाएगा (अगले कचरा संग्रह में हटा दिया जाएगा, क्योंकि अब इसका कोई संदर्भ नहीं है)।

संक्षेप में, my_binary_to_list/1 केवल एक मैच संदर्भ और कोई उप बायनेरी बनाने की आवश्यकता नहीं है।

ध्यान दें कि my_binary_to_list/1 में मैच का संदर्भ तब समाप्त हो गया था जब पूरे बाइनरी का पता लगाया गया था। यदि बाइनरी के अंत तक पहुंचने से पहले चलना बंद हो जाता है तो क्या होता है? अनुकूलन अभी भी काम करेगा?

after_zero(<<0,T/binary>>) ->
    T;
after_zero(<<_,T/binary>>) ->
    after_zero(T);
after_zero(<<>>) ->
    <<>>.

हाँ यह होगा। संकलक दूसरे खंड में उप बाइनरी के निर्माण को हटा देगा:

...
after_zero(<<_,T/binary>>) ->
    after_zero(T);
...

लेकिन यह कोड उत्पन्न करेगा जो पहले खंड में एक उप बाइनरी बनाता है:

after_zero(<<0,T/binary>>) ->
    T;
...

इसलिए, after_zero/1 एक मैच संदर्भ और एक उप बाइनरी बनाता है (यह मानते हुए कि यह एक बाइनरी पास है जिसमें एक शून्य बाइट शामिल है)।

निम्नलिखित की तरह कोड भी अनुकूलित किया जाएगा:

all_but_zeroes_to_list(Buffer, Acc, 0) ->
    {lists:reverse(Acc),Buffer};
all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
    all_but_zeroes_to_list(T, Acc, Remaining-1);
all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

संकलक दूसरे और तीसरे खंड में उप बायनेरी के निर्माण को हटा देता है, और यह पहले क्लॉज में एक निर्देश जोड़ता है जो Buffer को मैच के संदर्भ से उप बाइनरी में परिवर्तित करता है (या Buffer पहले से ही बाइनरी है तो कुछ भी नहीं करें)।

लेकिन अधिक जटिल कोड में, कोई यह कैसे जान सकता है कि अनुकूलन लागू है या नहीं?

विकल्प bin_opt_info

बायनेरी ऑप्टिमाइज़ेशन के बारे में कंपाइलर प्रिंट के बारे में बहुत सारी जानकारी प्राप्त करने के लिए bin_opt_info विकल्प का उपयोग करें। इसे संकलक या erlc को दिया जा सकता है:

erlc +bin_opt_info Mod.erl

या एक पर्यावरण चर के माध्यम से पारित:

export ERL_COMPILER_OPTIONS=bin_opt_info

ध्यान दें कि bin_opt_info का अर्थ आपके Makefile s में जोड़ा गया एक स्थायी विकल्प नहीं है, क्योंकि यह उत्पन्न होने वाले सभी संदेशों को समाप्त नहीं किया जा सकता है। इसलिए, पर्यावरण के माध्यम से विकल्प पारित करना ज्यादातर मामलों में सबसे व्यावहारिक दृष्टिकोण है।

चेतावनी निम्नानुसार है:

./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: sub binary is used or returned
./efficiency_guide.erl:62: Warning: OPTIMIZED: creation of sub binary delayed

यह स्पष्ट करने के लिए कि चेतावनियाँ किस कोड को संदर्भित करती हैं, निम्न उदाहरणों में चेतावनियों को उन टिप्पणियों के रूप में डाला जाता है, जिनका उल्लेख वे उदाहरण के लिए करते हैं:

after_zero(<<0,T/binary>>) ->
         %% NOT OPTIMIZED: sub binary is used or returned
    T;
after_zero(<<_,T/binary>>) ->
         %% OPTIMIZED: creation of sub binary delayed
    after_zero(T);
after_zero(<<>>) ->
    <<>>.

पहले खंड के लिए चेतावनी कहती है कि एक उप बाइनरी के निर्माण में देरी नहीं की जा सकती है, क्योंकि इसे वापस कर दिया जाएगा। दूसरे खंड के लिए चेतावनी कहती है कि एक उप बाइनरी नहीं बनाई जाएगी (अभी तक)।

अनुपयोगी चर

कंपाइलर यह पता लगाता है कि कोई चर अप्रयुक्त है या नहीं। निम्न में से प्रत्येक कार्य के लिए एक ही कोड बनाया गया है:

count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
count1(<<>>, Count) -> Count.

count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
count2(<<>>, Count) -> Count.

count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
count3(<<>>, Count) -> Count.

प्रत्येक पुनरावृत्ति में, बाइनरी में पहले 8 बिट्स को छोड़ दिया जाएगा, मिलान नहीं किया जाएगा।

4.4 ऐतिहासिक नोट

R12B में बाइनरी हैंडलिंग में काफी सुधार किया गया था। क्योंकि R11B में कुशल कोड R12B में कुशल नहीं हो सकता है, और इसके विपरीत, इस दक्षता गाइड के पहले के संशोधनों में R11B में बाइनरी हैंडलिंग के बारे में कुछ जानकारी शामिल थी।