Profiling
يوفر نموذج Profile
أدوات لمساعدة المطورين على تحسين أداء شيفرتهم. عند استخدامه، يقوم بأخذ قياسات على الشيفرة التي تعمل، وينتج مخرجات تساعدك على فهم مقدار الوقت المستغرق في سطر (أسطر) فردية. الاستخدام الأكثر شيوعًا هو تحديد "اختناقات" كأهداف للتحسين.
Profile
ينفذ ما يُعرف بـ "العينة" أو statistical profiler. يعمل من خلال أخذ تتبع خلفي بشكل دوري أثناء تنفيذ أي مهمة. كل تتبع خلفي يلتقط الوظيفة الحالية قيد التشغيل ورقم السطر، بالإضافة إلى السلسلة الكاملة من استدعاءات الوظائف التي أدت إلى هذا السطر، ومن ثم يُعتبر "لقطة" للحالة الحالية للتنفيذ.
إذا كانت الكثير من وقت التشغيل الخاص بك تُقضى في تنفيذ سطر معين من الشيفرة، فسوف يظهر هذا السطر بشكل متكرر في مجموعة جميع تتبعات الأخطاء. بعبارة أخرى، فإن "تكلفة" سطر معين - أو في الواقع، تكلفة تسلسل استدعاءات الدوال حتى بما في ذلك هذا السطر - تتناسب مع مدى تكرار ظهوره في مجموعة جميع تتبعات الأخطاء.
لا يوفر ملف التعريف العيني تغطية كاملة سطرًا بسطر، لأن تتبع الخلفيات يحدث على فترات (بشكل افتراضي، 1 مللي ثانية على أنظمة Unix و10 مللي ثانية على Windows، على الرغم من أن الجدولة الفعلية تخضع لحمولة نظام التشغيل). علاوة على ذلك، كما تم مناقشته أدناه، نظرًا لأن العينات تُجمع في مجموعة فرعية نادرة من جميع نقاط التنفيذ، فإن البيانات التي تجمعها أداة التعريف العيني تخضع للضوضاء الإحصائية.
على الرغم من هذه القيود، فإن أدوات تحليل العينة تتمتع بقوة كبيرة:
- لا تحتاج إلى إجراء أي تعديلات على الشيفرة الخاصة بك لأخذ قياسات التوقيت.
- يمكنه التعمق في الشيفرة الأساسية لجوليا وحتى (اختياريًا) في مكتبات C و Fortran.
- من خلال تشغيل "بشكل غير متكرر" هناك القليل جداً من عبء الأداء؛ أثناء التقييم، يمكن أن يعمل كودك بسرعة قريبة من السرعة الأصلية.
لذلك، يُوصى بأن تحاول استخدام أداة تحليل العينة المدمجة قبل التفكير في أي بدائل.
Basic usage
دعنا نعمل مع حالة اختبار بسيطة:
julia> function myfunc()
A = rand(200, 200, 400)
maximum(A)
end
من الجيد أن تقوم بتشغيل الكود الذي تنوي تقييم أدائه مرة واحدة على الأقل (ما لم تكن ترغب في تقييم مترجم JIT الخاص بـ Julia):
julia> myfunc() # run once to force compilation
الآن نحن مستعدون لتحديد ملف تعريف هذه الدالة:
julia> using Profile
julia> @profile myfunc()
لرؤية نتائج التحليل، هناك العديد من المتصفحات الرسومية. واحدة من "عائلات" المرئيات تعتمد على FlameGraphs.jl، مع كل عضو في العائلة يقدم واجهة مستخدم مختلفة:
- VS Code هو IDE كامل مع دعم مدمج لتصور الملف الشخصي
- ProfileView.jl هو مُصوِّر مستقل يعتمد على GTK
- ProfileVega.jl يستخدم VegaLight ويتكامل بشكل جيد مع دفاتر Jupyter
- StatProfilerHTML.jl ينتج HTML ويقدم بعض الملخصات الإضافية، كما أنه يتكامل بشكل جيد مع دفاتر Jupyter
- ProfileSVG.jl يعرض SVG
- PProf.jl يخدم موقعًا محليًا لفحص الرسوم البيانية، والرسوم النارية، والمزيد
- ProfileCanvas.jl هو واجهة عرض ملف تعريف تعتمد على HTML canvas، تُستخدم بواسطة Julia VS Code extension، ولكن يمكنها أيضًا إنشاء ملفات HTML تفاعلية.
نهج مستقل تمامًا لتصور الملف الشخصي هو PProf.jl، والذي يستخدم أداة pprof
الخارجية.
هنا، على الرغم من ذلك، سنستخدم العرض القائم على النص الذي يأتي مع المكتبة القياسية:
julia> Profile.print()
80 ./event.jl:73; (::Base.REPL.##1#2{Base.REPL.REPLBackend})()
80 ./REPL.jl:97; macro expansion
80 ./REPL.jl:66; eval_user_input(::Any, ::Base.REPL.REPLBackend)
80 ./boot.jl:235; eval(::Module, ::Any)
80 ./<missing>:?; anonymous
80 ./profile.jl:23; macro expansion
52 ./REPL[1]:2; myfunc()
38 ./random.jl:431; rand!(::MersenneTwister, ::Array{Float64,3}, ::Int64, ::Type{B...
38 ./dSFMT.jl:84; dsfmt_fill_array_close_open!(::Base.dSFMT.DSFMT_state, ::Ptr{F...
14 ./random.jl:278; rand
14 ./random.jl:277; rand
14 ./random.jl:366; rand
14 ./random.jl:369; rand
28 ./REPL[1]:3; myfunc()
28 ./reduce.jl:270; _mapreduce(::Base.#identity, ::Base.#scalarmax, ::IndexLinear,...
3 ./reduce.jl:426; mapreduce_impl(::Base.#identity, ::Base.#scalarmax, ::Array{F...
25 ./reduce.jl:428; mapreduce_impl(::Base.#identity, ::Base.#scalarmax, ::Array{F...
كل سطر من هذا العرض يمثل نقطة معينة (رقم السطر) في الشيفرة. يتم استخدام المسافة البادئة للإشارة إلى تسلسل استدعاءات الدوال المتداخلة، حيث تكون الأسطر الأكثر تداخلاً أعمق في تسلسل الاستدعاءات. في كل سطر، يكون "الحقل" الأول هو عدد تتبعات الأخطاء (عينات) المأخوذة في هذا السطر أو في أي دوال تم تنفيذها بواسطة هذا السطر. الحقل الثاني هو اسم الملف ورقم السطر والحقل الثالث هو اسم الدالة. لاحظ أن أرقام الأسطر المحددة قد تتغير مع تغير شيفرة جوليا؛ إذا كنت ترغب في المتابعة، فمن الأفضل تشغيل هذا المثال بنفسك.
في هذا المثال، يمكننا أن نرى أن الدالة الرئيسية التي تم استدعاؤها موجودة في الملف event.jl
. هذه هي الدالة التي تشغل REPL عند تشغيلك لجوليا. إذا قمت بفحص السطر 97 من REPL.jl
، سترى أن هذه هي النقطة التي يتم فيها استدعاء الدالة eval_user_input()
. هذه هي الدالة التي تقيم ما تكتبه في REPL، ونظرًا لأننا نعمل بشكل تفاعلي، تم استدعاء هذه الدوال عندما أدخلنا @profile myfunc()
. السطر التالي يعكس الإجراءات المتخذة في الماكرو @profile
.
السطر الأول يُظهر أنه تم أخذ 80 تتبعًا عند السطر 73 من event.jl
، لكن ليس من الصحيح أن هذا السطر كان "مكلفًا" بمفرده: السطر الثالث يكشف أن جميع هذه التتبعيات الـ 80 تم تفعيلها فعليًا داخل استدعائه لـ eval_user_input
، وهكذا. للعثور على العمليات التي تأخذ الوقت فعليًا، نحتاج إلى النظر أعمق في سلسلة الاستدعاءات.
السطر "المهم" الأول في هذا الإخراج هو هذا:
52 ./REPL[1]:2; myfunc()
REPL
يشير إلى حقيقة أننا عرفنا myfunc
في REPL، بدلاً من وضعه في ملف؛ إذا كنا قد استخدمنا ملفًا، فإن هذا سيظهر اسم الملف. الـ [1]
يظهر أن الدالة myfunc
كانت أول تعبير تم تقييمه في جلسة REPL هذه. تحتوي السطر 2 من myfunc()
على استدعاء لـ rand
، وكان هناك 52 (من أصل 80) تتبعات حدثت في هذا السطر. أدناه، يمكنك رؤية استدعاء لـ dsfmt_fill_array_close_open!
داخل dSFMT.jl
.
قليلاً أسفل، سترى:
28 ./REPL[1]:3; myfunc()
السطر 3 من myfunc
يحتوي على استدعاء لـ maximum
، وكان هناك 28 (من أصل 80) تتبعات تم أخذها هنا. أدناه، يمكنك رؤية الأماكن المحددة في base/reduce.jl
التي تقوم بتنفيذ العمليات المستهلكة للوقت في دالة maximum
لهذا النوع من بيانات الإدخال.
بشكل عام، يمكننا أن نستنتج بشكل مؤقت أن توليد الأرقام العشوائية يكلف تقريبًا ضعف تكلفة العثور على العنصر الأقصى. يمكننا زيادة ثقتنا في هذه النتيجة من خلال جمع المزيد من العينات:
julia> @profile (for i = 1:100; myfunc(); end)
julia> Profile.print()
[....]
3821 ./REPL[1]:2; myfunc()
3511 ./random.jl:431; rand!(::MersenneTwister, ::Array{Float64,3}, ::Int64, ::Type...
3511 ./dSFMT.jl:84; dsfmt_fill_array_close_open!(::Base.dSFMT.DSFMT_state, ::Ptr...
310 ./random.jl:278; rand
[....]
2893 ./REPL[1]:3; myfunc()
2893 ./reduce.jl:270; _mapreduce(::Base.#identity, ::Base.#scalarmax, ::IndexLinea...
[....]
بشكل عام، إذا كان لديك N
عينات تم جمعها على خط، يمكنك توقع وجود عدم يقين على مستوى sqrt(N)
(باستثناء مصادر الضوضاء الأخرى، مثل مدى انشغال الكمبيوتر بمهام أخرى). الاستثناء الرئيسي لهذه القاعدة هو جمع القمامة، الذي يعمل بشكل غير متكرر ولكنه يميل إلى أن يكون مكلفًا جدًا. (نظرًا لأن جامع القمامة في جوليا مكتوب بلغة C، يمكن اكتشاف مثل هذه الأحداث باستخدام وضع الإخراج C=true
الموضح أدناه، أو عن طريق استخدام ProfileView.jl.)
هذا يوضح تفريغ "الشجرة" الافتراضي؛ بديل آخر هو تفريغ "المسطح"، الذي يجمع العدّات بشكل مستقل عن تداخلها:
julia> Profile.print(format=:flat)
Count File Line Function
6714 ./<missing> -1 anonymous
6714 ./REPL.jl 66 eval_user_input(::Any, ::Base.REPL.REPLBackend)
6714 ./REPL.jl 97 macro expansion
3821 ./REPL[1] 2 myfunc()
2893 ./REPL[1] 3 myfunc()
6714 ./REPL[7] 1 macro expansion
6714 ./boot.jl 235 eval(::Module, ::Any)
3511 ./dSFMT.jl 84 dsfmt_fill_array_close_open!(::Base.dSFMT.DSFMT_s...
6714 ./event.jl 73 (::Base.REPL.##1#2{Base.REPL.REPLBackend})()
6714 ./profile.jl 23 macro expansion
3511 ./random.jl 431 rand!(::MersenneTwister, ::Array{Float64,3}, ::In...
310 ./random.jl 277 rand
310 ./random.jl 278 rand
310 ./random.jl 366 rand
310 ./random.jl 369 rand
2893 ./reduce.jl 270 _mapreduce(::Base.#identity, ::Base.#scalarmax, :...
5 ./reduce.jl 420 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
253 ./reduce.jl 426 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
2592 ./reduce.jl 428 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
43 ./reduce.jl 429 mapreduce_impl(::Base.#identity, ::Base.#scalarma...
إذا كان لديك كود يحتوي على تكرار، فإن نقطة قد تكون مربكة هي أن سطرًا في دالة "طفل" يمكن أن يجمع المزيد من العدادات مما هو موجود من إجمالي تتبعات العودة. اعتبر تعريفات الدوال التالية:
dumbsum(n::Integer) = n == 1 ? 1 : 1 + dumbsum(n-1)
dumbsum3() = dumbsum(3)
إذا كنت ستقوم بملف تعريف dumbsum3
، وتم أخذ تتبع للعودة أثناء تنفيذها dumbsum(1)
، فسيبدو تتبع العودة كما يلي:
dumbsum3
dumbsum(3)
dumbsum(2)
dumbsum(1)
نتيجة لذلك، يحصل هذا الدالة الفرعية على 3 عدادات، على الرغم من أن الوالد يحصل على واحدة فقط. تمثل "الشجرة" هذا بشكل أوضح بكثير، ولهذا السبب (من بين أسباب أخرى) من المحتمل أن تكون هذه هي الطريقة الأكثر فائدة لرؤية النتائج.
Accumulation and clearing
تتراكم النتائج من @profile
في ذاكرة مؤقتة؛ إذا قمت بتشغيل عدة قطع من الشيفرة تحت 4d61726b646f776e2e436f64652822222c20224070726f66696c652229_40726566
، فإن Profile.print()
ستظهر لك النتائج المجمعة. يمكن أن يكون هذا مفيدًا جدًا، ولكن في بعض الأحيان تريد البدء من جديد؛ يمكنك القيام بذلك باستخدام Profile.clear()
.
Options for controlling the display of profile results
Profile.print
يحتوي على المزيد من الخيارات مما وصفناه حتى الآن. دعنا نرى الإعلان الكامل:
function print(io::IO = stdout, data = fetch(); kwargs...)
دعونا نناقش أولاً الحجج الموضعية، ثم الحجج بالكلمات المفتاحية:
io
– يتيح لك حفظ النتائج في ذاكرة مؤقتة، مثل ملف، ولكن الإعداد الافتراضي هو الطباعة إلىstdout
(وحدة التحكم).data
– يحتوي على البيانات التي تريد تحليلها؛ بشكل افتراضي يتم الحصول عليها منProfile.fetch()
، والذي يستخرج تتبعات الأخطاء من ذاكرة مؤقتة مُعدة مسبقًا. على سبيل المثال، إذا كنت تريد تحليل المحلل، يمكنك أن تقول:data = copy(Profile.fetch()) Profile.clear() @profile Profile.print(stdout, data) # Prints the previous results Profile.print() # Prints results from Profile.print()
يمكن أن تكون وسائط الكلمات الرئيسية أي مجموعة من:
format
– تم تقديمه أعلاه، يحدد ما إذا كانت تتبع الأخطاء مطبوعة مع (افتراضي،:tree
) أو بدون (:flat
) تباين يشير إلى هيكل الشجرة.C
– إذا كانتtrue
، يتم عرض تتبعات الأخطاء من كود C و Fortran (عادةً ما يتم استبعادها). حاول تشغيل المثال التمهيدي باستخدامProfile.print(C = true)
. يمكن أن يكون هذا مفيدًا للغاية في تحديد ما إذا كان كود جوليا أو كود C هو الذي يسبب عنق الزجاجة؛ كما أن تعيينC = true
يحسن أيضًا من قابلية تفسير التداخل، على حساب زيادة طول تفريغ الملف الشخصي.combine
– تحتوي بعض أسطر الشيفرة على عمليات متعددة؛ على سبيل المثال،s += A[i]
تحتوي على كل من مرجع المصفوفة (A[i]
) وعملية الجمع. هذه تتوافق مع أسطر مختلفة في الشيفرة الآلية المولدة، وبالتالي قد يتم التقاط عنوانين أو أكثر مختلفين خلال تتبعات الأخطاء على هذا السطر.combine = true
تجمعها معًا، ومن المحتمل أن يكون هذا ما تريده عادةً، ولكن يمكنك توليد مخرجات بشكل منفصل لكل مؤشر تعليمات فريد معcombine = false
.maxdepth
– يحدد الإطارات على عمق أعلى منmaxdepth
في تنسيق:tree
.sortedby
– يتحكم في الترتيب في تنسيق:flat
. يقوم:filefuncline
(افتراضي) بالفرز حسب سطر المصدر، بينما يقوم:count
بالفرز حسب عدد العينات المجمعة.noisefloor
– يحدد الإطارات التي تقل عن مستوى الضوضاء الاستدلالي للعينة (ينطبق فقط على التنسيق:tree
). قيمة مقترحة لتجربتها هي 2.0 (القيمة الافتراضية هي 0). هذا المعامل يخفي العينات التي يكون فيهاn <= noisefloor * √N
، حيثn
هو عدد العينات في هذا السطر، وN
هو عدد العينات للمتصل.mincount
– يحدد الإطارات التي تحتوي على أقل منmincount
حدوث.
File/function names are sometimes truncated (with ...
), and indentation is truncated with a +n
at the beginning, where n
is the number of extra spaces that would have been inserted, had there been room. If you want a complete profile of deeply-nested code, often a good idea is to save to a file using a wide displaysize
in an IOContext
:
open("/tmp/prof.txt", "w") do s
Profile.print(IOContext(s, :displaysize => (24, 500)))
end
Configuration
@profile
فقط يجمع تتبعات الأخطاء، وتحدث التحليلات عندما تستدعي Profile.print()
. بالنسبة لحساب طويل الأمد، من الممكن تمامًا أن يتم ملء المخزن المخصص لتخزين تتبعات الأخطاء. إذا حدث ذلك، تتوقف تتبعات الأخطاء لكن حسابك يستمر. ونتيجة لذلك، قد تفوت بعض بيانات التوصيف المهمة (ستتلقى تحذيرًا عندما يحدث ذلك).
يمكنك الحصول على المعلمات ذات الصلة وتكوينها بهذه الطريقة:
Profile.init() # returns the current settings
Profile.init(n = 10^7, delay = 0.01)
n
هو العدد الإجمالي لمؤشرات التعليمات التي يمكنك تخزينها، مع قيمة افتراضية تبلغ 10^6
. إذا كانت تتبع التعليمات النموذجي لديك هو 20 مؤشر تعليمات، فيمكنك جمع 50000 تتبع، مما يشير إلى عدم اليقين الإحصائي أقل من 1%. قد يكون هذا جيدًا بما يكفي لمعظم التطبيقات.
نتيجة لذلك، من المرجح أن تحتاج إلى تعديل delay
، المعبر عنه بالثواني، والذي يحدد مقدار الوقت الذي تحصل عليه جوليا بين اللقطات لأداء الحسابات المطلوبة. قد لا تحتاج الوظيفة التي تستغرق وقتًا طويلاً إلى تتبعات متكررة. الإعداد الافتراضي هو delay = 0.001
. بالطبع، يمكنك تقليل التأخير وكذلك زيادته؛ ومع ذلك، فإن عبء العمل الناتج عن التقييم ينمو بمجرد أن يصبح التأخير مشابهًا للوقت اللازم لأخذ تتبع (~30 ميكروثانية على جهاز الكمبيوتر المحمول للمؤلف).
Memory allocation analysis
أحد أكثر التقنيات شيوعًا لتحسين الأداء هو تقليل تخصيص الذاكرة. توفر جوليا العديد من الأدوات لقياس ذلك:
@time
يمكن قياس إجمالي مقدار التخصيص باستخدام @time
، @allocated
و @allocations
، ويمكن غالبًا استنتاج الأسطر المحددة التي تؤدي إلى التخصيص من خلال التحليل عبر تكلفة جمع القمامة التي تتكبدها هذه الأسطر. ومع ذلك، في بعض الأحيان يكون من الأكثر كفاءة قياس مقدار الذاكرة المخصصة مباشرةً بواسطة كل سطر من التعليمات البرمجية.
GC Logging
بينما يسجل @time
إحصائيات عالية المستوى حول استخدام الذاكرة وجمع القمامة على مدار تقييم تعبير ما، يمكن أن يكون من المفيد تسجيل كل حدث لجمع القمامة، للحصول على فكرة بديهية عن مدى تكرار تشغيل جامع القمامة، ومدة تشغيله في كل مرة، وكمية القمامة التي يجمعها في كل مرة. يمكن تفعيل ذلك باستخدام GC.enable_logging(true)
، مما يتسبب في تسجيل جوليا إلى stderr في كل مرة يحدث فيها جمع للقمامة.
Allocation Profiler
تتطلب هذه الوظيفة على الأقل جوليا 1.8.
يسجل محلل التخصيص تتبع المكدس، النوع، وحجم كل تخصيص أثناء تشغيله. يمكن استدعاؤه باستخدام Profile.Allocs.@profile
.
تُرجع هذه المعلومات حول التخصيصات كمصفوفة من كائنات Alloc
، ملفوفة في كائن AllocResults
. أفضل طريقة لتصور هذه المعلومات حاليًا هي باستخدام الحزم PProf.jl و ProfileCanvas.jl، والتي يمكن أن تصور مكدسات الاستدعاء التي تقوم بأكبر عدد من التخصيصات.
يحتوي ملف تعريف التخصيص على عبء كبير، لذا يمكن تمرير وسيط sample_rate
لتسريعه عن طريق جعله يتخطى بعض التخصيصات. تمرير sample_rate=1.0
سيجعله يسجل كل شيء (وهو بطيء)؛ sample_rate=0.1
سيسجل فقط 10% من التخصيصات (أسرع)، وهكذا.
لم تكن الإصدارات القديمة من جوليا قادرة على التقاط الأنواع في جميع الحالات. في الإصدارات القديمة من جوليا، إذا رأيت تخصيصًا من النوع Profile.Allocs.UnknownType
، فهذا يعني أن أداة التحليل لا تعرف نوع الكائن الذي تم تخصيصه. حدث هذا بشكل رئيسي عندما كان التخصيص يأتي من الشيفرة المولدة التي ينتجها المترجم. راجع issue #43688 لمزيد من المعلومات.
منذ Julia 1.11، يجب أن تحتوي جميع التخصيصات على نوع مُبلغ عنه.
لمزيد من التفاصيل حول كيفية استخدام هذه الأداة، يرجى الاطلاع على الحديث التالي من JuliaCon 2022: https://www.youtube.com/watch?v=BFvpwC8hEWQ
Allocation Profiler Example
في هذا المثال البسيط، نستخدم PProf لتصور ملف التخصيص. يمكنك استخدام أداة تصور أخرى بدلاً من ذلك. نجمع الملف (مع تحديد معدل العينة)، ثم نقوم بتصوره.
using Profile, PProf
Profile.Allocs.clear()
Profile.Allocs.@profile sample_rate=0.0001 my_function()
PProf.Allocs.pprof()
هنا مثال أكثر تفصيلاً، يوضح كيف يمكننا ضبط معدل العينة. عدد جيد من العينات الذي يجب أن نستهدفه هو حوالي 1 - 10 آلاف. إذا كان العدد كبيرًا جدًا، يمكن أن يتعرض مرئي الملف الشخصي للإرهاق، وسيكون التقييم بطيئًا. إذا كان العدد قليلًا جدًا، فلن تحصل على عينة تمثيلية.
julia> import Profile
julia> @time my_function() # Estimate allocations from a (second-run) of the function
0.110018 seconds (1.50 M allocations: 58.725 MiB, 17.17% gc time)
500000
julia> Profile.Allocs.clear()
julia> Profile.Allocs.@profile sample_rate=0.001 begin # 1.5 M * 0.001 = ~1.5K allocs.
my_function()
end
500000
julia> prof = Profile.Allocs.fetch(); # If you want, you can also manually inspect the results.
julia> length(prof.allocs) # Confirm we have expected number of allocations.
1515
julia> using PProf # Now, visualize with an external tool, like PProf or ProfileCanvas.
julia> PProf.Allocs.pprof(prof; from_c=false) # You can optionally pass in a previously fetched profile result.
Analyzing 1515 allocation samples... 100%|████████████████████████████████| Time: 0:00:00
Main binary filename not available.
Serving web UI on http://localhost:62261
"alloc-profile.pb.gz"
ثم يمكنك عرض الملف الشخصي عن طريق الانتقال إلى http://localhost:62261، والملف الشخصي محفوظ على القرص. راجع حزمة PProf لمزيد من الخيارات.
Allocation Profiling Tips
كما هو مذكور أعلاه، استهدف حوالي 1-10 آلاف عينة في ملفك الشخصي.
لاحظ أننا نقوم بأخذ عينات بشكل موحد في فضاء جميع التخصيصات، ولا نقوم بوزن عيناتنا حسب حجم التخصيص. لذا قد لا تعطي ملف تخصيص معين تمثيلاً تمثيليًا لمكان تخصيص معظم البايتات في برنامجك، ما لم تكن قد قمت بتعيين sample_rate=1
.
يمكن أن تأتي التخصيصات من المستخدمين الذين يقومون بإنشاء كائنات مباشرة، ولكن يمكن أن تأتي أيضًا من داخل وقت التشغيل أو يتم إدراجها في الشيفرة المترجمة للتعامل مع عدم استقرار النوع. يمكن أن يكون النظر إلى عرض "الشيفرة المصدرية" مفيدًا لعزلها، ثم يمكن أن تكون أدوات خارجية أخرى مثل Cthulhu.jl
مفيدة لتحديد سبب التخصيص.
Allocation Profile Visualization Tools
هناك العديد من أدوات تصور التحليل الآن التي يمكنها جميعًا عرض ملفات تعريف التخصيص. إليك قائمة صغيرة ببعض من أبرزها التي نعرفها:
VSCode's built-in profile visualizer (
@profview_allocs
) [docs needed]عرض النتائج مباشرة في REPL
- يمكنك فحص النتائج في REPL عبر
Profile.Allocs.fetch()
، لرؤية تتبع المكدس ونوع كل تخصيص.
- يمكنك فحص النتائج في REPL عبر
Line-by-Line Allocation Tracking
طريقة بديلة لقياس التخصيصات هي بدء جوليا مع خيار سطر الأوامر --track-allocation=<setting>
، حيث يمكنك اختيار none
(الإعداد الافتراضي، عدم قياس التخصيص)، user
(قياس تخصيص الذاكرة في كل مكان باستثناء كود جوليا الأساسي)، أو all
(قياس تخصيص الذاكرة في كل سطر من كود جوليا). يتم قياس التخصيص لكل سطر من الكود المترجم. عند إنهاء جوليا، يتم كتابة النتائج التراكمية إلى ملفات نصية مع .mem
مضافة بعد اسم الملف، وتقع في نفس الدليل الذي يوجد فيه ملف المصدر. يسرد كل سطر العدد الإجمالي للبايتات المخصصة. يحتوي Coverage
package على بعض أدوات التحليل الأساسية، على سبيل المثال لترتيب الأسطر حسب عدد البايتات المخصصة.
في تفسير النتائج، هناك بعض التفاصيل المهمة. تحت إعداد user
، ستظهر السطر الأول من أي دالة يتم استدعاؤها مباشرة من REPL تخصيصًا بسبب الأحداث التي تحدث في كود REPL نفسه. والأهم من ذلك، أن تجميع JIT يضيف أيضًا إلى أعداد التخصيص، لأن جزءًا كبيرًا من مترجم جوليا مكتوب بلغة جوليا (وغالبًا ما يتطلب التجميع تخصيص الذاكرة). الإجراء الموصى به هو إجبار التجميع عن طريق تنفيذ جميع الأوامر التي تريد تحليلها، ثم استدعاء Profile.clear_malloc_data()
لإعادة تعيين جميع عدادات التخصيص. أخيرًا، نفذ الأوامر المطلوبة واخرج من جوليا لتحفيز إنشاء ملفات .mem
.
--track-allocation
يغير توليد الشيفرة لتسجيل التخصيصات، وبالتالي قد تكون التخصيصات مختلفة عما يحدث بدون الخيار. نوصي باستخدام allocation profiler بدلاً من ذلك.
External Profiling
حاليًا تدعم جوليا Intel VTune
و OProfile
و perf
كأدوات خارجية للتعقب.
اعتمادًا على الأداة التي تختارها، قم بتجميع مع USE_INTEL_JITEVENTS
، USE_OPROFILE_JITEVENTS
و USE_PERF_JITEVENTS
مضبوطة على 1 في Make.user
. يتم دعم عدة علامات.
قبل تشغيل جوليا، قم بتعيين متغير البيئة ENABLE_JITPROFILING
إلى 1.
الآن لديك العديد من الطرق لاستخدام تلك الأدوات! على سبيل المثال، مع OProfile
يمكنك تجربة تسجيل بسيط:
>ENABLE_JITPROFILING=1 sudo operf -Vdebug ./julia test/fastmath.jl
>opreport -l `which ./julia`
أو بشكل مشابه مع perf
:
$ ENABLE_JITPROFILING=1 perf record -o /tmp/perf.data --call-graph dwarf -k 1 ./julia /test/fastmath.jl
$ perf inject --jit --input /tmp/perf.data --output /tmp/perf-jit.data
$ perf report --call-graph -G -i /tmp/perf-jit.data
هناك العديد من الأشياء المثيرة للاهتمام التي يمكنك قياسها حول برنامجك، للحصول على قائمة شاملة يرجى قراءة Linux perf examples page.
تذكر أن perf
يحفظ لكل تنفيذ ملف perf.data
الذي، حتى بالنسبة للبرامج الصغيرة، يمكن أن يصبح كبيرًا جدًا. أيضًا، يقوم وحدة LLVM الخاصة بـ perf بحفظ كائنات التصحيح مؤقتًا في ~/.debug/jit
، تذكر أن تقوم بتنظيف هذا المجلد بشكل متكرر.