Как работает приведение типов в Голанге? - PullRequest
0 голосов
/ 24 декабря 2018

Моя задача состоит в том, чтобы загружать и сохранять двоичные файлы с числами, и эти числа могут быть легко сохранены в uint32/float32.Это будет около 2 ГБ на диске и все должно быть в памяти тоже.

Моя программа потребует много математических операций, а функции / методы стандартных библиотек Голанга требуют int/float64 параметров, и я долженприведите мои числа к int/float64

Простой тест (https://play.golang.org/p/A52-wBo3Z34) дал следующий результат:

$: go test -bench=. 
goos: linux
goarch: amd64
pkg: gotrade
BenchmarkCast-4             1000       1519964 ns/op
BenchmarkNoCast-4           3000        373340 ns/op
PASS
ok      gotrade 2.843s

Это ясно показывает, что приведение типов довольно дорого.

Недостатки int и float64:

  • требуется почти двойная память

Достоинства int и float64:

  • избегает много операций приведения типов

Пожалуйста, предложите мне способ справиться с этой ситуацией, я что-то здесь упускаю?

Должны ли мы всегда выбирать int и float64, если нам требуются внешние вычисления через стандартные библиотеки?

1 Ответ

0 голосов
/ 25 декабря 2018

У вас есть несколько ошибок в вашей логике, тесте и ваших предположениях.

Что касается приведения, ваши результаты показывают, что цикл for выполняется 1000 раз.Так как вы выполняете цикл 1M раз, это фактически делает 1 миллиард операций приведения ... не слишком потертый.

На самом деле, я немного переработал ваш код:

const (
    min = float64(math.SmallestNonzeroFloat32)
    max = float64(math.MaxFloat32)
)

func cast(in float64) (out float32, err error) {

    // We need to guard here, as casting from float64 to float32 looses precision
    // Therefor, we might get out of scope.
    if in < min {
        return 0.00, fmt.Errorf("%f is smaller than smallest float32 (%f)", in, min)
    } else if in > max {
        return 0.00, fmt.Errorf("%f is bigger than biggest float32 (%f)", in, max)
    }

    return float32(in), nil
}

// multi64 uses a variadic in parameter, in order to be able
// to use the multiplication with arbitrary length.
func multi64(in ...float64) (result float32, err error) {

    // Necessary to set it to 1.00, since float64's null value is 0.00...
    im := float64(1.00)

    for _, v := range in {
        im = im * v
    }

    // We only need to cast once.
    // You DO want to make the calculation with the original precision and only
    // want to do the casting ONCE. However, this should not be done here - but in the
    // caller, as the caller knows on how to deal with special cases.
    return cast(im)
}

// multi32 is a rather non-sensical wrapper, since the for loop
// could easily be done in the caller.
// It is only here for comparison purposes.
func multi32(in ...float32) (result float32) {
    result = 1.00
    for _, v := range in {
        result = result * v
    }
    return result
}

// openFile is here for comparison to show that you can do
// a... fantastic metric ton of castings in comparison to IO ops.
func openFile() error {
    f, err := os.Open("cast.go")
    if err != nil {
        return fmt.Errorf("Error opening file")
    }
    defer f.Close()

    br := bufio.NewReader(f)
    if _, _, err := br.ReadLine(); err != nil {
        return fmt.Errorf("Error reading line: %s", err)
    }

    return nil
}

со следующим тестовым кодом

func init() {
    rand.Seed(time.Now().UTC().UnixNano())
}
func BenchmarkCast(b *testing.B) {
    b.StopTimer()

    v := rand.Float64()
    var err error

    b.ResetTimer()
    b.StartTimer()

    for i := 0; i < b.N; i++ {
        if _, err = cast(v); err != nil {
            b.Fail()
        }
    }
}

func BenchmarkMulti32(b *testing.B) {
    b.StopTimer()

    vals := make([]float32, 10)

    for i := 0; i < 10; i++ {
        vals[i] = rand.Float32() * float32(i+1)
    }

    b.ResetTimer()
    b.StartTimer()

    for i := 0; i < b.N; i++ {
        multi32(vals...)
    }
}
func BenchmarkMulti64(b *testing.B) {

    b.StopTimer()

    vals := make([]float64, 10)
    for i := 0; i < 10; i++ {
        vals[i] = rand.Float64() * float64(i+1)
    }

    var err error
    b.ResetTimer()
    b.StartTimer()

    for i := 0; i < b.N; i++ {
        if _, err = multi64(vals...); err != nil {
            b.Log(err)
            b.Fail()
        }
    }
}

func BenchmarkOpenFile(b *testing.B) {
    var err error
    for i := 0; i < b.N; i++ {
        if err = openFile(); err != nil {
            b.Log(err)
            b.Fail()
        }
    }
}

Вы получаете что-то вроде этого

BenchmarkCast-4         1000000000           2.42 ns/op
BenchmarkMulti32-4       300000000           5.04 ns/op
BenchmarkMulti64-4       200000000           8.19 ns/op
BenchmarkOpenFile-4         100000          19591 ns/op

Так что, даже с этим относительно глупым и неоптимизированным кодом, злоумышленник является эталоном openFile.

Теперь давайтемы рассматриваем это в перспективе.19,562 нс равняется 0,019562 миллисекундам.Средний человек может воспринимать латентность около 20 миллисекунд.Так что даже те 100 000 ( "сто тысяч" ) открытий файлов, чтения строк и закрытий файлов примерно в 1000 раз быстрее, чем может воспринять человек.

Приведение, по сравнению с этим, несколько на порядки быстрее - поэтому разыгрывайте все, что вам нравится, узким местом будет I / O.

Edit

Что оставляет вопрос, почему вы делаетене импортировать значения как float64 на первое место?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...