передать константный указатель большой структуры в функцию или канал go - PullRequest
0 голосов
/ 16 февраля 2020

Как передать константный указатель большой структуры в функцию или go канал. Цель этого запроса:

  1. Избегать случайного изменения указателя функцией
  2. Избегать копирования объекта структуры при передаче в функцию / канал

Эта функциональность очень распространена в C ++, C#, Java, но как мы можем добиться того же в golang?

============== Обновление 2 ===================

Спасибо, @zarkams, @mkopriva и @peterSO. Оптимизация компилятора привела к одинаковому результату как byValue (), так и byPointer (). Изменили функции byValue () и byPointer (), добавив data.array [0] = reverse (data.array [0]) , просто чтобы компилятор не делал функции встроенными.

func byValue(data Data) int {
    data.array[0] = reverse(data.array[0])
    return len(data.array)
}

func byPointer(data *Data) int {
    data.array[0] = reverse(data.array[0])
    return len(data.array)
}

func reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

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

C:\Users\anikumar\Desktop\TestGo>go test -bench=.
goos: windows
goarch: amd64
BenchmarkByValue-4         18978             58228 ns/op               3 B/op          1 allocs/op
BenchmarkByPointer-4    40034295                33.1 ns/op             3 B/op          1 allocs/op
PASS
ok      _/C_/Users/anikumar/Desktop/TestGo      3.336s

C:\Users\anikumar\Desktop\TestGo>go test -gcflags -N -run=none -bench=.
goos: windows
goarch: amd64
BenchmarkByValue-4         20961             59380 ns/op               3 B/op          1 allocs/op
BenchmarkByPointer-4    31386213                36.5 ns/op             3 B/op          1 allocs/op
PASS
ok      _/C_/Users/anikumar/Desktop/TestGo      3.909s 

============= Update = ==================

На основе отзывов от @zerkms я создал тест, чтобы найти разницу в производительности между копией по значению и копией по указатель.

package main

import (
    "log"
    "time"
)

const size = 99999

// Data ...
type Data struct {
    array [size]string
}

func main() {
    // Preparing large data
    var data Data
    for i := 0; i < size; i++ {
        data.array[i] = "This is really long string"
    }

    // Starting test
    const max = 9999999999
    start := time.Now()
    for i := 0; i < max; i++ {
        byValue(data)
    }
    elapsed := time.Since(start)
    log.Printf("By Value took %s", elapsed)

    start = time.Now()
    for i := 0; i < max; i++ {
        byPointer(&data)
    }
    elapsed = time.Since(start)
    log.Printf("By Pointer took %s", elapsed)
}

func byValue(data Data) int {
    data.array[0] = reverse(data.array[0])
    return len(data.array)
}

func byPointer(data *Data) int {
    data.array[0] = reverse(data.array[0])
    return len(data.array)
}

func reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

После 10 итераций вышеприведенной программы я не нашел никакой разницы во времени выполнения.

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:52:03 By Value took 5.2798936s
2020/02/16 15:52:09 By Pointer took 5.3466306s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:52:18 By Value took 5.3596692s
2020/02/16 15:52:23 By Pointer took 5.2724685s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:52:29 By Value took 5.2359938s
2020/02/16 15:52:34 By Pointer took 5.2838676s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:52:42 By Value took 5.8374936s
2020/02/16 15:52:49 By Pointer took 6.9524342s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:53:40 By Value took 5.4364867s
2020/02/16 15:53:46 By Pointer took 5.8712875s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:53:54 By Value took 5.5481591s
2020/02/16 15:54:00 By Pointer took 5.5600314s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:54:10 By Value took 5.4753771s
2020/02/16 15:54:16 By Pointer took 6.4368084s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:54:24 By Value took 5.4783356s
2020/02/16 15:54:30 By Pointer took 5.5312314s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:54:39 By Value took 5.4853542s
2020/02/16 15:54:45 By Pointer took 5.5541164s

C:\Users\anikumar\Desktop\TestGo>TestGo.exe
2020/02/16 15:54:57 By Value took 5.4633856s
2020/02/16 15:55:03 By Pointer took 5.4863226s

Похоже @zerkms прав. Это не из-за языка, это из-за современного оборудования.

Ответы [ 2 ]

3 голосов
/ 17 февраля 2020

Я думаю, что это действительно хороший вопрос, и я не знаю, почему люди отметили его. (То есть исходный вопрос об использовании «константного указателя» для передачи большой структуры.)

Простой ответ заключается в том, что Go не может указать, что функция (или канал) берет указатель не собирается изменять указанную вещь. По сути, создатель функции должен задокументировать, что функция не изменит структуру.

@ Anil8753, поскольку вы явно упоминаете каналы, я должен кое-что объяснить. Обычно при использовании канала вы передаете данные в другую go -программу. Если вы передаете указатель на структуру, то отправитель должен быть осторожен, чтобы не изменять структуру после того, как она была отправлена ​​(по крайней мере, пока получатель мог ее прочитать), и наоборот. Это создаст гонку данных.

По этой причине я обычно передаю структуры по значению с каналами. Если вам нужно создать что-то в отправителе для исключительного использования получателя, то создайте структуру (в куче) и отправьте указатель на нее в канале и никогда не используйте ее снова (даже присваивая указателю значение nil, чтобы сделать это явным) .

@ zerkms отмечает, что прежде чем приступить к оптимизации, вы должны понять, что происходит, и провести измерения. Однако в этом случае очевидное преимущество в производительности заключается в отсутствии копирования памяти. Независимо от того, происходит ли это, когда структура имеет размер 1 КБ, 1 МБ или 1 ГБ, наступит момент, когда вы захотите передать «ссылку» (ie указатель на структуру), а не значение (если вы знаете, что структура выиграла) не будет изменено или не волнует, если это так).

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

3 голосов
/ 16 февраля 2020

Бессмысленные микробенчмарки дают бессмысленные результаты.


В Go все аргументы передаются по значению.


Для вашего обновленного примера (Test Go),

$ go version
go version devel +6917529cc6 Sat Feb 15 16:40:12 2020 +0000 linux/amd64
$ go run microbench.go
2020/02/16 13:12:56 By Value took 2.877045229s
2020/02/16 13:12:59 By Pointer took 2.875847918s
$

Go компиляторы обычно оптимизируют компиляторы. Например,

./microbench.go:39:6: can inline byValue
./microbench.go:43:6: can inline byPointer
./microbench.go:26:10: inlining call to byValue
./microbench.go:33:12: inlining call to byPointer

Нет затрат на вызов функции. Следовательно, нет разницы во времени выполнения.

microbench.go:

package main

import (
    "log"
    "time"
)

const size = 99999

// Data ...
type Data struct {
    array [size]string
}

func main() {
    // Preparing large data
    var data Data
    for i := 0; i < size; i++ {
        data.array[i] = "This is really long string"
    }

    // Starting test
    const max = 9999999999
    start := time.Now()
    for i := 0; i < max; i++ {
        byValue(data)
    }
    elapsed := time.Since(start)
    log.Printf("By Value took %s", elapsed)

    start = time.Now()
    for i := 0; i < max; i++ {
        byPointer(&data)
    }
    elapsed = time.Since(start)
    log.Printf("By Pointer took %s", elapsed)
}

func byValue(data Data) int {
    return len(data.array)
}

func byPointer(data *Data) int {
    return len(data.array)
}


ADDENDUM

Комментарий : @ Anil8753 Следует также отметить, что стандартная библиотека Go имеет пакет тестирования, который предоставляет некоторые полезные функции для тестирования производительности. Например, рядом с вашим основным файлом. go добавьте файл main_test. go (имя файла важно) и добавьте к нему эти два теста , а затем из папки выполните эту команду go test -run = none -bench =., это выведет на экран, сколько операций было выполнено, сколько времени заняла одна операция, сколько памяти потребовалось одной операции и сколько выделений потребовалось. - компиляторы mkopriva


Go обычно оптимизируют компиляторы. Современное оборудование обычно сильно оптимизировано.

Для микробенчума mkopriva,

$ go test microbench.go mkopriva_test.go -bench=.
BenchmarkByValue-4     1000000000   0.289 ns/op   0 B/op   0 allocs/op
BenchmarkByPointer-4   1000000000   0.575 ns/op   0 B/op   0 allocs/op
$ 

Однако для микробенчмарка mkopriva с раковиной,

$ go test microbench.go sink_test.go -bench=.
BenchmarkByValue-4     1000000000   0.576 ns/op   0 B/op   0 allocs/op
BenchmarkByPointer-4   1000000000   0.592 ns/op   0 B/op   0 allocs/op
$ 

mkopriva_test.go:

package main

import (
    "testing"
)

func BenchmarkByValue(b *testing.B) {
    var data Data
    b.ReportAllocs()
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        byValue(data)
    }
}

func BenchmarkByPointer(b *testing.B) {
    var data Data
    b.ReportAllocs()
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        byPointer(&data)
    }
}

sink_test.go:

package main

import (
    "testing"
)

var banchInt int

func BenchmarkByValue(b *testing.B) {
    var data Data
    b.ReportAllocs()
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        banchInt = byValue(data)
    }
}

func BenchmarkByPointer(b *testing.B) {
    var data Data
    b.ReportAllocs()
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        banchInt = byPointer(&data)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...