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

Developer

Send message

Попытался распараллелить "в лоб" на 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 переменных было выбрано неудачно и вносит путаницу, то тут я полностью согласен.

Дело в том, что const — это не константа, а именно read-only переменная. Как правило, константам можно присваивать только скалярные значения, переменной же const в JS можно присвоить любой объект. Кроме того, const можно использовать и в циклах, если это перечисление. Например следующий код будет прекрасно работать:


const fruits = ["apple", "banana", "orange"];

for (const fruit of fruits) {
    console.log(fruit);
    // fruit = "dog" вызовет: TypeError: Assignment to constant variable.
}

Еще одна причина почему лучше везде где возможно использовать const, это интроспекция в IDE. Например, в TypeScript следующая проверка будет работать:


const eventHandler: Function | null = getHandler();

if (eventHandler === null) {
    return;
}
// с этого места eventHandler имеет тип Function

// ... много кода

// можно безопасно вызывать
// eventHandler - точно не null
eventHandler();

А если же заменить const eventHandler на let eventHandler, то код не скомпилируется, поскольку между первичной проверкой на null и вызовом eventHandler() мог присвоиться null уже где-то в другом месте.


Кстати, похожее декларирование переменных есть в Kotlin, только там используются ключевые слова var, val.
https://kotlinlang.org/docs/reference/basic-syntax.html#defining-variables

Я бы рекомендовал игру Choice of Robots. Это не визуальная новелла, а текстовая адвенчура без единой картинки. Но сюжет настолько разнообразный и интересный, что игра просто захватывает с первых минут.

Английский язык там бывает местами сложный, но в большинстве своем читается на одном дыхании. Небольшой отрывок в качестве примера (прим. Arniel — это имя моего робота):


You realize you're driving yourself crazy by sitting here listening to this output, which will be only a tiny portion of the overall data. You decide to take a long walk around Stanford's campus. It's a nice day out and when you return, Arniel has moved on to the topic of which vegetables go best in a savory crepe.

But for better or for worse, Arniel appears to be ingesting the spirit of the Internet: obsessed with minutia, occasionally factually incorrect, and mistrustful of authority. (-Military) (++Autonomy)

You leave the program running overnight. When you wake up the next morning, you ask Arniel, «How do you feel?»

«Confused and angry,» says Arniel. «But also smart.»

You're ecstatic—you never programmed Arniel to say anything like that.

«Is the United States a good guy or a bad guy, Master?» Arniel asks. «I am confused about this. Some people seem very convinced one way or the other.»

Arniel is even trying to make ethical decisions! This is all very exciting. But what will you say?

«The United States government in itself is neither good nor evil. It is simply powerful; and the people in it choose every day to use that power for good or evil.»

«It seems that it is good to be powerful,» Arniel ventures.

«Yes,» you say darkly.

«The optimal strategy appears to be to become powerful first, and decide whether to be good or evil later,» Arniel says.

«Are you planning to become powerful, then?» you say to Arniel, slightly amused.

Arniel nods and says earnestly, «Yes.» (+++Autonomy) (+Military)

Information

Rating
8,039-th
Location
Austin, Texas, США
Date of birth
Registered
Activity