قياس وقت التنفيذ في برنامج Go

تتمثل إحدى العمليات الشائعة عند تطوير تطبيق Go ، أو قياسه لإيجاد طرق لتحسينه ، في قياس وقت تنفيذ الوظيفة. لقد قدمت موضوعوحدة المعالجة المركزية وتحديد سمات الذاكرة في Goبالفعل ، ولكن هذا مختلف وأكثر ملاءمة لقياس وقت تنفيذ الوظيفة المخصصة.

حدد الفاصل الزمني بين تعبيرين: استخدامtime.Since

time.Sinceهي وظيفة يوفرهاtimeحزمة المكتبة القياسية التي تأخذ ملفTimevalue ، وتحسب الفرق مع الوقت الحالي ، وترجع قيمةDurationالقيمة ، وهي نوع int64 بامتدادString()الطريقة المرفقة ، والتي توفر تدوينًا وديًا للإنسان للوقت المحسوب.

import (
    "fmt"
    "time"
)

func main() { start := time.Now()

<span style="color:#75715e">//... do something

fmt.Println(time.Since(start)) }

داخليا،time.Sinceهو اختصار لtime.Now().Sub(t).

استخدامdeferلقياس الوقت داخل دالة

يمكنك تجريد هذا باستخدامdefer:

package main

import ( “log” “time” )

func runningtime(s string) (string, time.Time) { log.Println("Start: ", s) return s, time.Now() }

func track(s string, startTime time.Time) { endTime := time.Now() log.Println("End: ", s, “took”, endTime.Sub(startTime)) }

func execute() { defer track(runningtime(“execute”)) time.Sleep(3 * time.Second) }

func main() { execute() }

قم بتحديد أداء الوظيفة من خلال تشغيلها عدة مرات: استخدم ملحقtestingصفقة

هذه مكتبة بسيطة كتبتها ، تحسب عدد الأحرف الرونية في سلسلة.

runecount.go

package runecount

import “unicode/utf8”

func RuneCount(s string) int { return len([]rune(s)) }

func RuneCount2(s string) int { return utf8.RuneCountInString(s) }

أي من طريقتين لحساب الأحرف الرونية في سلسلة أسرع؟ نكتشف من خلال كتابة معيار. تعيش المعايير في*_test.goملف ، مثل الاختبارات المنتظمة ، ويمكنه البقاء مع طرق الاختبار التي تبدأ بـTest*، لكنهم يبدأون بـBenchmark*بدلا من:

runecount_test.go

package runecount

import “testing”

func BenchmarkRuneCount(b testing.B) { s := “Gophers are amazing 😁” for i := 0; i < b.N; i++ { RuneCount(s) } } func BenchmarkRuneCount2(b testing.B) { s := “Gophers are amazing 😁” for i := 0; i < b.N; i++ { RuneCount2(s) } }

أقوم بتشغيل هذا عن طريق التنفيذ ، في نفس المجلد حيث توجد الملفات:

go test -bench=.

هذا يتسبب في تشغيل جميع المعايير. لقد حصلت على ملف واحد فقط ، ولكن إذا كان لديك الكثير ، يمكنك تحديد الملف الذي تريد تشغيله.

يقوم كل معيار بتشغيل الحلقة التي تمر عبر متغير bN ، والذي يتم تحديده تلقائيًا بواسطة العداء القياسي ، حتى يصبح المتوسط مستقرًا بدرجة كافية لتحديد النتيجة.

هذا هو الناتج الذي أحصل عليه على جهاز Mac الخاص بي:

# flavio @ Flavios-MacBook-Pro in ~/go/src/github.com/flaviocopes/snippets/benchmark on git:master x [18:32:26]
$ go test -bench=.
BenchmarkRuneCount-2            20000000               115 ns/op
BenchmarkRuneCount2-2           30000000                44.1 ns/op
PASS
ok      github.com/flaviocopes/snippets/benchmark       3.812s

20000000و30000000عدد العمليات التي يتم تشغيلها.

يقوم الأمر بإرجاع نتائج الاختبار: تم تشغيله 20 مليون مرةRuneCount()، والتي استغرقت في المتوسط 163 نانوثانية ، ثم تم تشغيلها 30 مليون مرةRuneCount2()، والتي استغرقت في المتوسط 74.2 نانوثانية لكل تكرار.

وبالتاليBenchmark1الذي يعملRuneCount()، أبطأ بمقدار 2x منRuneCount2()، التي تستخدم المضمنةutf8.RuneCountInStringوظيفة. لا عجب هنا ، كماutf8.RuneCountInStringتم تحسينه بشكل كبير لهذه المهمة المحددة.


بدلا من استخدام ملفاتgo test، يمكنك أيضًا الاتصالtesting.Benchmarkمن أمر:

package main

import ( “fmt” “testing” “runecount” )

func BenchmarkRuneCount(b testing.B) { s := “Gophers are amazing 😁” for i := 0; i < b.N; i++ { RuneCount(s) } } func BenchmarkRuneCount2(b testing.B) { s := “Gophers are amazing 😁” for i := 0; i < b.N; i++ { RuneCount2(s) } }

func main() { fmt.Println(testing.Benchmark(BenchmarkRuneCount)) fmt.Println(testing.Benchmark(BenchmarkRuneCount2)) }

أين تقرأ المزيد


المزيد من دروس Go: