Лично я обычно на новых для меня языках люблю писать Решето Эратосфена в качестве "Hello, world!".
Моя версия на Go
выгдядит так:
Файл erato-go-bool.go:
package main
import "fmt"
import "math"
import "flag"
func main() {
var N int
flag.IntVar(&N, "N", 100, "")
flag.Parse()
fmt.Printf("%d\n", N)
seive := make([]bool, N)
limit := int(math.Sqrt(float64(N))) + 1
for i := 2; i < limit; i++ {
if !seive[i] {
for j := i * i; j < N; j += i {
seive[j] = true
}
}
}
count := 0
for i := 2; i < N; i++ {
if !seive[i] {
count++
}
}
fmt.Printf("%d\n", count)
}
И первый вопрос, который приходит в голову - а насколько это быстро работает?
Некоторое время назад я уже писал, как использовал решето для тестирования STL'евского контейнера std::vector
на разных компиляторах.
Сейчас я провел похожее сравнение между Go, C++ и C.
Итак, первый кандитат - версия на Go с использованием типа bool
(см. выше). Второй - тоже на Go, но с использованием типа int
.
Файл erato-go-int.go:
package main
import "fmt"
import "math"
import "flag"
func main() {
var N int
flag.IntVar(&N, "N", 100, "")
flag.Parse()
fmt.Printf("%d\n", N)
seive := make([]int, N)
limit := int(math.Sqrt(float64(N))) + 1
for i := 2; i < limit; i++ {
if seive[i] == 0 {
for j := i * i; j < N; j += i {
seive[j] = 1
}
}
}
count := 0
for i := 2; i < N; i++ {
if seive[i] == 0 {
count++
}
}
fmt.Printf("%d\n", count)
}
Далее идет версия на С++. Макрос TYPE
позволяет переключать программу для нужно типа в контейнере (int
или bool
):
Файл erato-cxx.cpp:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cmath>
int main(int argc, char* argv[]) {
int n = argc > 1 ? std::atoi(argv[1]) : 100;
std::cout << n << std::endl;
int sqrt_n = static_cast<int>(std::sqrt(static_cast<double>(n))) + 1;
std::vector<TYPE> S(n, true);
for (int i = 2; i < sqrt_n; ++i)
if (S[i])
for (int j = i*i; j < n; j+=i)
S[j] = false;
int count = 0;
for (int i = 2; i < n; ++i)
if (S[i])
count++;
std::cout << count << std::endl;
return 0;
}
Ну и для полноты картины версия на С:
Файл erator-c-int.c:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <math.h>
int main(int argc, char* argv[]) {
int n = argc > 1 ? atoi(argv[1]) : 100;
int* S;
int count;
int sz = n * sizeof(*S);
int i, j;
printf("%d\n", n);
long sqrt_n = sqrt(n) + 1;
S = malloc(sz);
memset(S, 0, sz);
for (i = 2; i < sqrt_n; ++i)
if (S[i] == 0)
for (j = i*i; j < n; j+=i)
S[j] = 1;
count = 0;
for (i = 2; i < n; ++i)
if (S[i] == 0)
count++;
printf("%d\n", count);
free(S);
return 0;
}
Ну и Makefile
для удобного запуска.
Файл Makefile:
.SILENT:
all:
$(MAKE) run 2>&1 | tee log
$(MAKE) parse-log
run: go-bool go-int cxx-int cxx-bool c-int
N ?= 100000000
go-bool:
echo $@
6g erato-$@.go
6l -o erato-$@ erato-$@.6
time -p -f %e ./erato-$@ -N=$(N)
go-int:
echo $@
6g erato-$@.go
6l -o erato-$@ erato-$@.6
time -p -f %e ./erato-$@ -N=$(N)
cxx-bool:
echo $@
g++ -o erato-$@ \
-O3 -funroll-all-loops -fomit-frame-pointer \
-DTYPE=bool erato-cxx.cpp
time -p -f %e ./erato-$@ $(N)
cxx-int:
echo $@
g++ -o erato-$@ \
-O3 -funroll-all-loops -fomit-frame-pointer \
-DTYPE=int erato-cxx.cpp
time -p -f %e ./erato-$@ $(N)
c-int:
echo $@
gcc -o erato-$@ -lm \
-O3 -funroll-all-loops -fomit-frame-pointer erato-$@.c
time -p -f %e ./erato-$@ $(N)
parse-log:
printf "%10s %10s %8s %5s\n" "Language" N Count Time ; \
(echo "------------------------------------") ; \
while read type ; do \
read N && \
read count && \
read time && \
printf "%10s %10s %8s %5s\n" $$type $$N $$count $$time ; \
done < log
Запускал я все это под Ubuntu 64-bit. Компилятор C и C++ - gcc 4.4.1. Компилятор Go
- последний из официального репозитория.
Запускаем:
make N=100000000
и получаем следующее:
Language N Count Time
------------------------------------
go-bool 100000000 5761455 3.96
go-int 100000000 5761455 6.58
cxx-int 100000000 5761455 6.76
cxx-bool 100000000 5761455 2.20
c-int 100000000 5761455 6.47
Получается, что сделал всех С++ с использованием std::vector<bool>
для хранения массива. Затем идет Go
тоже с типом bool
. А С, С++ с std::vector<int>
и Go
с int'ом примерно равны.
Update: После экспериментов в комментариях выходит, что и на С, и на С++ можно добиться равного быстродействия, если использовать битовые поля. Просто в С++ это синтаксически проще, но не более того.
Посты по теме:
Есть идеи почему вариант с C такой медленный?
ОтветитьУдалитьПочему медленный? Нормальный. Это С++ быстрый. Правильная оптимизация шаблонов (и правильное программирование на шаблонах) в С++ может дать оргомный выйгрыш. В С такой роскоши нет.
ОтветитьУдалитьа почему на сях вы не используете calloc, а вместо него malloc+memset?
ОтветитьУдалитьКстати говоря, что интересно, тип bool на С++ он же однобайтный. Интересно как это влияет.
ОтветитьУдалитьПочему не calloc? Не знаю, привычка ;-).
ОтветитьУдалитьА в плане bool в С++, то тут дело не bool как таковом, а в реализации вектора для bool. Там точно отдельная имплементация, оптимизированная на хранение битов. Причем на разных версиях STL и разных компиляторах может работать с разной скоростью, и не факт, что быстрее. В моем прошлом эксперименте (ссылка в тексте) это было видно.
Я скомпилировал ваш пример для Си на MSVC 2003 для int и unsigned char + использовал calloc. unsigned char работает быстрее. (для 10000000 итераций время int на моей машине 1.36 сек, время unsigned char 1.047сек).
ОтветитьУдалитьНе могли бы вы повторить у себя замеры для Си с использованием int / unsigned char, чтобы статистика была полной?
Хм, первый раз скомпилировал без оптимизации. Повторил с максимальной оптимизацией (ключ /Ox). Оба варианта стали быстрее, но unsigned char все равно впереди:
ОтветитьУдалитьint 1.109
unsigned char 0.703
Да, у меня версия на С с unsigned char быстрее:
ОтветитьУдалитьgo-bool 100000000 5761455 3.98
go-int 100000000 5761455 6.56
cxx-int 100000000 5761455 6.49
cxx-bool 100000000 5761455 2.25
c-int 100000000 5761455 7.55
c-uchar 100000000 5761455 3.90
Могу предположить, что в Go модели хранения в памяти близки к С, что объясняет схожесть замеров для int и unsigned char.
ОтветитьУдалитьСпасибо.
ОтветитьУдалитьЯ переделал сишный вариант на использование массива битов и получил существенное ускорение. Таки да, дело не в скорости плюсов, а в выбранном типе памяти. Я так думаю, что существенный прирост скорости получается за счет того, что в случае с битовым массивом большая часть элементов такого массива остается в кеше поцессора. Как следствие -- имеем меньшие задержки при обращениях к реальной памяти.
ОтветитьУдалитьСобственно код программы использующей битовый массив (к счастью у меня уже была готовая реализация битового массива для си):
ОтветитьУдалить#include
#include
#include
#include
#define BV(x) (1< 1 ? atoi(argv[1]) : 100;
BitArray *S;
int count;
int sz = n * sizeof(*S) / 32 + 1;
int i, j;
long sqrt_n;
printf("n = %d\n", n);
sqrt_n = sqrt(n) + 1;
S = calloc(1, sz);
for (i = 2; i < sqrt_n; ++i)
if (!bit_get(S, i))
for (j = i*i; j < n; j+=i)
bit_on(S, j);
count = 0;
for (i = 2; i < n; ++i)
if (!bit_get(S, i))
count++;
printf("%d\n", count);
free(S);
return 0;
}
Поскольку вставить код в комментарии нормально не получается, то я закинул его на Launchpad:
ОтветитьУдалитьbzr branch lp:~bialix/+junk/erato-c
Круто! Скомпилил ваш вариант. Получилось даже чуть быстрее С++:
ОтветитьУдалитьLanguange N Count Time
------------------------------------
go-bool 100000000 5761455 3.90
go-int 100000000 5761455 7.10
cxx-int 100000000 5761455 6.47
cxx-bool 100000000 5761455 2.20
c-int 100000000 5761455 6.54
c-uchar 100000000 5761455 3.74
c-bits 100000000 5761455 2.12
Значит дело действительно просто в попадании в кеш.
Думаю если вы проделаете замеры для си с битовым массивом, а также для go с битовым массивом, то результаты будут похожи на C++ с битовым вектором.
ОтветитьУдалитьИтого: каким будет правильный ответ на вопрос в заголовке? они все примерно равны? но в плюсах есть готовая либа сложных алгоритмов. плюсы остаются в плюсах, а готовые либы рулят.
Факт. Обновил резюме поста. Выходит, что в плане скорости побеждает не язык, а архитектура.
ОтветитьУдалитьКстати, недавно наткнулся на автоматический "сравниватель" производительности и используемой памяти программ на разных языках
ОтветитьУдалитьНапример, C++ vs Go:
http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=gpp&lang2=go