All streams
Search
Write a publication
Pull to refresh
4
0
Ilya Pirogov @ilyapirogov

Developer

Send message

4 физических / 8 логических https://pastebin.com/xfVW6g3h. Странно, что результат так сильно от процессора зависит. Надо будет на нормальном железе потестить.


Попробую без атомиков реализовать позже.

Раз уж пошла такая пьянка с распараллеливанием на других языках, то решил немного доработать этот код. Сделал обычный пул на нативных каналах, на моем ноуте получилось примерно на 20-30% эффективнее, чем в варианте с 30 000 горутин.


package main

import (
    "flag"
    "math"
    "runtime"
    "sync"
    "sync/atomic"
)

var n uint64
var workers int
var wg sync.WaitGroup

func worker(arr []uint64, inners <- chan uint64) {
    for k1 := range inners {
        for k2 := k1; k2 <= n/k1; k2++ {
            var val uint64

            if k1 != k2 {
                val = k1 + k2
            } else {
                val = k1
            }

            atomic.AddUint64(&arr[k1*k2], val)
        }
    }
    wg.Done()
}

func main() {
    flag.Uint64Var(&n,"max", 1000000, "max N")
    flag.IntVar(&workers, "workers", runtime.NumCPU() / 2, "num of workers")
    flag.Parse()

    sqrtN := uint64(math.Sqrt(float64(n)))
    arr := make([]uint64, n+1)
    pool := make(chan uint64, sqrtN)

    for k1 := uint64(1); k1 <= sqrtN; k1++ {
        pool <- k1
    }
    close(pool)

    for i := 0; i < workers; i++ {
        wg.Add(1)
        go worker(arr, pool)
    }

    wg.Wait()
    println(arr[n])
}

Попытался распараллелить "в лоб" на Golang. Правда я не уверен, что не накосячил с синхронизацией, но по идеи если массив не меняется, то получать ссылку на значение можно и без лока. Во всяком случае результирующее значение для миллиарда он считает правильное:


package main

import (
    "flag"
    "math"
    "sync"
    "sync/atomic"
)

var wg sync.WaitGroup
func calc(n uint64) uint64 {
    arr := make([]uint64, n+1)

    for k1 := uint64(1); k1 <= uint64(math.Sqrt(float64(n))); k1++ {
        wg.Add(1)
        go func(t1 uint64) {
            for k2 := t1; k2 <= n/t1; k2++ {
                var val uint64

                if t1 != k2 {
                    val = t1 + k2
                } else {
                    val = t1
                }

                atomic.AddUint64(&arr[t1*k2], val)
            }
            wg.Done()
        }(k1)
    }

    wg.Wait()
    return arr[n]
}

var maxN = flag.Uint64("max", 1000000, "max N")

func main() {
    flag.Parse()
    println(calc(*maxN))
}

В итоге для миллиарда получилось 111.69 sec, т.е. примерно на 40 sec быстрее, чем на C++. 0xd34df00d если будет возможность, попробуйте протестировать у себя. Интересно какой будет результат.

На мой взгляд некорректно сравнивать код оперирующий int64 с кодом оперирующим int32. Если в C++ варианте заменить int64_t на int32_t, то скорость выполнения у меня возрастает почти в два раза. Так что в данном случае надо везде long использовать.

Заменил int64 на uint64 и результат получился значительно лучше:


goos: linux
goarch: amd64
BenchmarkMaxN_1_000_000-8                 20      86956243 ns/op     8003608 B/op          1 allocs/op
BenchmarkMaxN_10_000_000-8                 1    1594835918 ns/op    80003168 B/op          2 allocs/op
BenchmarkMaxN_100_000_000-8                1    21094544631 ns/op   800006144 B/op         1 allocs/op
BenchmarkMaxN_1_000_000_000-8              1    299162328158 ns/op  8000004096 B/op        1 allocs/op
PASS
ok      _/home/ilya/Projects/habr-test-1    324.019s

Я думаю Golang достаточно умный чтобы выполнить это один раз. Попробовал вынести это в переменную, но не заметил разницы.

Я компилировал ваш код с теми же аргументами: clang++ -O3 -march=native -o habr-test-cpp ./habr-test-1.cpp. Правда, судя по всему, у меня clang 6

Вариант на Golang:


package main

import (
    "math"
    "testing"
)

func BenchmarkMaxN_1_000_000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        calc(1000000)
    }
}

func BenchmarkMaxN_10_000_000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        calc(10000000)
    }
}

func BenchmarkMaxN_100_000_000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        calc(100000000)
    }
}

func BenchmarkMaxN_1_000_000_000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        calc(1000000000)
    }
}

func calc(n int64) int64 {
    arr := make([]int64, n+1)

    for k1 := int64(1); k1 <= int64(math.Sqrt(float64(n))); k1++ {
        for k2 := k1; k2 <= n/k1; k2++ {
            var val int64

            if k1 != k2 {
                val = k1 + k2
            } else {
                val = k1
            }

            arr[k1*k2] += val
        }
    }

    return arr[n]
}

И результат на моем древнем ноутбуке с Intel® Core(TM) i7-2860QM CPU @ 2.50GHz и 16 Gb оперативки:


$ go test -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkMaxN_1_000_000-8                 20      97029314 ns/op     8003696 B/op          1 allocs/op  // 0.097 sec
BenchmarkMaxN_10_000_000-8                 1    2065155756 ns/op    80003072 B/op          1 allocs/op  // 2.06 sec
BenchmarkMaxN_100_000_000-8                1    27831236382 ns/op   800006144 B/op         1 allocs/op  // 27.83 sec
BenchmarkMaxN_1_000_000_000-8              1    386078665273 ns/op  8000004096 B/op        1 allocs/op  // 386.07 sec
PASS
ok      _/home/ilya/Projects/habr-test-1    418.363s

При этом С++ вариант у меня выполняется:


  • Для 1 000 000 за 0.044 sec
  • Для 1 000 000 000 за 158.79 sec

Т.е. на моем ноуте Golang медленнее С++ чуть более чем в 2 раза. Потребление памяти одинаковое.


Кстати, еще примечательно, что C++ код выполнялся в абсолютной тишине, а от Golang кулеры завизжали как сумасшедшие. Хотя возможно, это как-то связанно со спецификой работы Benchmark в Golang.


PS Слишком долго форматировал сообщение, опередили :D

Так а как они проблему с бездорожьем в России решать собираются? Или они дальше Москвы (определенных районов Москвы?) не будут ездить?
Если вы покупаете Home Edition, то вы можете выбрать еще 5 аккаунтов, у которых будет полностью такой же набор услуг. У каждого будет свой собственный 1ТБ хранилища, на своим собственном аккаунте.
image
Возможно он так и делает. В данном случае я больше хотел показать, что в Golang уже все необходимое для измерения производительности есть: golang.org/pkg/testing Так что нету необходимости придумывать велосипеды.

А зачем такой сложный тест? Мне кажется было бы нагляднее, если упростить его до максимума. Тем более, что Golang предоставляет такой замечательный инструмент для тестирования из коробки.


package main

import (
    "testing"
    "time"
)

var val1 = 10
var val2 = 1000

func longComparison(val int) bool  {
    time.Sleep(100 * time.Millisecond)
    return true
}

func BenchmarkComplexIfDiv(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if val1 > val2 && val1%2 == 0 {
        }
    }
}

func BenchmarkComplexIfConj(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if val1 > val2 && val1&1 == 0 {
        }
    }
}

func BenchmarkComplexLong(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if val1 > val2 && longComparison(val1) {
        }
    }
}

func BenchmarkTwoSimpleIf(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if val1 <= val2 {
            continue
        }

        if val1%2 == 0 {
        }
    }
}

// $ go test -bench .
// BenchmarkComplexIfDiv-8      2000000000           0.85 ns/op
// BenchmarkComplexIfConj-8     2000000000           0.85 ns/op
// BenchmarkComplexLong-8       2000000000           0.57 ns/op
// BenchmarkTwoSimpleIf-8       2000000000           0.58 ns/op

Кстати, забавно, но BenchmarkComplexLong такой же быстрый как и вариант с continue

А еще можно скооперироваться и взять Office 356 Home на шестерых. Тогда у каждого будет по полному пакету офиса и 1ТБ хранилища за $1.4 (90 руб) в месяц. Собственно, именно это я и сделал.

P.S. Ах да, еще это включает 60 минут Skype в месяц. Звонить можно во многие страны, в частности в Россию можно тоже, но только на стационарные телефоны. Однако я этим совершенно не пользуюсь.
Разве торрент не поддерживает сжатие данных на лету? Что-то мне подсказывает, что 16 Мб нулей при сжатии gzip превратятся в те-же самые 4 Кб.

git stash — отложить все текущие изменения в локальное хранилище
git stash pop — забрать последние сохраненные изменения из хранилища


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

Я бы сказал: уверенно прет напролом с фанфарами.
В принципе да, вы правы, это считалось дурным кодом только с `var`. В таком случае код по ссылке выше полностью корректен.
Замыкания в цикле — это анти-паттерн, который нужно избегать всеми силами. В остальном, в данном примере все корректно отработано.

Если вы про то, что ключевое слово const для определения read-only переменных было выбрано неудачно и вносит путаницу, то тут я полностью согласен.

Information

Rating
Does not participate
Location
Austin, Texas, США
Date of birth
Registered
Activity