Почему вызов функции через плагин go быстрее, чем вызов функции напрямую

#go #plugins #benchmarking

# #Вперед #Плагины #сравнительный анализ

Вопрос:

Я хотел сравнить плагины go, чтобы увидеть, какова разница в производительности. Итак, я создал файл main.go со следующим кодом:

 package main

import (
    "math/rand"
    "strings"
)

// RandString generates and returns a random 50 character string
func RandString(n int) string {
    rand.Seed(int64(n))
    chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
        "abcdefghijklmnopqrstuvwxyz"  
        "0123456789")
    var b strings.Builder
    for i := 0; i < 50; i   {
        b.WriteRune(chars[rand.Intn(len(chars))])
    }
    return b.String()
}
 

Затем я превращаю это в plugin.so досье.

 go build -buildmode=plugin -o plugin.so main.go
 

Затем я написал две контрольные функции, чтобы проверить производительность запуска встроенной функции по сравнению с запуском ее через плагин go.

 // BenchmarkRandString tests generating a random string without a go plugin
func BenchmarkRandString(b *testing.B) {
    for i := 0; i < b.N; i   {
        RandString(rand.Int())
    }
}

// BenchmarkPluginRandString tests generating a random string with a go plugin
func BenchmarkPluginRandString(b *testing.B) {
    plug, err := plugin.Open("./plugin.so")
    if err != nil {
        panic(err)
    }

    randString, err := plug.Lookup("RandString")
    if err != nil {
        panic(err)
    }

    randFunc, ok := randString.(func(n int) string)
    if !ok {
        panic("unexpected type from module symbol")
    }

    b.ResetTimer()

    for i := 0; i < b.N; i   {
        randFunc(rand.Int())
    }
}
 

Как я и ожидал, плагин работает немного медленнее, но не намного

BenchmarkRandString-12 128064 8600 нс / оп
BenchmarkPluginRandString-12 132007 8713 нс / оп

Затем я хотел добавить еще 2 теста, поэтому я добавил еще одну функцию для генерации случайного целого числа и построил плагин так же, как и раньше.

 // RandInt uses math/rand to return a random integer
func RandInt() int {
    return rand.Int()
}
 

Затем мои новые контрольные функции добавлены поверх двух предыдущих контрольных функций.

 // BenchmarkRandInt tests math/rand for generating random integers without a go plugin
func BenchmarkRandInt(b *testing.B) {
    for i := 0; i < b.N; i   {
        RandInt()
    }
}

// BenchmarkPluginRandInt uses a go plugin and tests math/rand for generating random integers
func BenchmarkPluginRandInt(b *testing.B) {
    plug, err := plugin.Open("./plugin.so")
    if err != nil {
        panic(err)
    }

    randInt, err := plug.Lookup("RandInt")
    if err != nil {
        panic(err)
    }

    randFunc, ok := randInt.(func() int)
    if !ok {
        panic("unexpected type from module symbol")
    }

    b.ResetTimer()

    for i := 0; i < b.N; i   {
        randFunc()
    }
}
 

Теперь, когда я снова запускаю тест, я получаю следующий результат:

BenchmarkRandInt-12 77320668 13,2 нс / оп
BenchmarkPluginRandInt-12 76371756 13,9 нс / оп
BenchmarkRandString-12 136243 8600 нс / оп
BenchmarkPluginRandString-12 142112 8564 нс / оп

Я могу запускать тест снова и снова, и BenchmarkRandString-12 всегда немного медленнее, чем BenchmarkPluginRandString-12, чего я не ожидал. Почему функция плагина go немного быстрее при таком сопоставлении?

У меня есть проект Github со всем исходным кодом, который я использую здесь: https://github.com/uberswe/goplugins/tree/4825172e011da9578553d113bac7933ca9ecd038

Ответ №1:

Что может быть медленнее с функцией «плагина», так это ее загрузка и утверждение типа. Как только вы это сделаете, не должно быть никакого снижения производительности по сравнению с функцией, определенной в вашем приложении.

Такие небольшие отклонения могут быть результатом управления внутренней памятью Go и сборки мусора. Например, если в вашем main_test.go файле я перемещаюсь BenchmarkPluginRandString() выше BenchmarkRandString() , то результат теста «обратный»: BenchmarkRandString() становится немного медленнее.

Чтобы избавиться от таких недетерминированных факторов, вы можете попробовать запускать тесты изолированно, например, запускать только по одному за раз с

 go test -bench BenchmarkRandString
 

и

 go test -bench BenchmarkPluginRandString
 

И сделайте это несколько раз, и вычислите среднее значение. Таким образом, заметной разницы нет.