0ralo
@0ralo
Python backend developer

Почему в моем тесте go быстрее c?

Решил я короче потестить 2 идентичных(по синтаксису, личное мнение) языка Go и C. Написал 2 одинаковых(надеюсь) скрипта для анализа.
main.go

package main

import "fmt"

func fibonaci(n int64) int64 {
	if n == 1 || n == 0 {
		return n
	}
	return fibonaci(n-1) + fibonaci(n-2)
}

func main() {
	fmt.Printf("%v", fibonaci(45))
}
main.c

#include <stdio.h>

long fibonaci(long n){
    if(n == 1 || n == 0)
        return n;
    return fibonaci(n - 1) + fibonaci(n - 2);
}

int main() {
    printf("%ld", fibonaci(45));
}


Сборка и тесты го делаю так:
#go build
#time ./gopractice

1134903170
real    0m5,508s
user    0m5,511s
sys     0m0,000s

Сборка и тесты Си делаю так:
#gcc main.c
#time ./a.out

1134903170
real    0m8,353s
user    0m8,348s
sys     0m0,005s

Доп инфа

gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

go version
go version go1.21.0 linux/amd64

  • Вопрос задан
  • 1392 просмотра
Пригласить эксперта
Ответы на вопрос 4
1. А почему оптимизации в GCC не включены?

2. Код не эквивалентный. Зачем ты меряешь скорость printf?

3. Почему всего 1 прогон? Может 3 секунды - входит в твою погрешность?
Прогони хотябы по 100 раз и посчитай отклонение
Ответ написан
TedBear
@TedBear
прочитать книгу Андрей Акиньшин: Профессиональный бенчмарк, и юзать либы, которые заточены под бенчмарки, а не принтфы
Ответ написан
Griboks
@Griboks
Потому что C вовсе не обязан быть быстрее любого другого языка. Бывают случаи, когда эквивалентные программы на каком-нибудь питоне работают быстрее. Это, разумеется, зависит от самого программиста, а не от языка - не существует никакой магии.

Почему в конкретно данном примере go оказался быстрее? Нужно провести сравнение байткода. Лично я вижу три узких места:
1. разные цепочки компиляции
2. вывод на экран
3. разные загрузчики

Но это всё, разумеется, ничего не значит, потому что в конечном итоге go оказался быстрее. Конечно, можно выкинуть всё "неудобное" для С, чтобы замедлить go, но это уже не будут эквивалентные программы.
Ответ написан
Комментировать
shurshur
@shurshur
Сисадмин, просто сисадмин...
Лет 20 назад коллеги сравнили реализации рекурсивного Фибоначчи на C и Java и Java оказалась быстрее (!!!). Декомпиляция показала, что gcc тех лет вставляет в тело каждой функции сохранение некоторых регистров типа push di; pop di; хотя в таком простом коде это вообще не имело смысла. А подобные мелочи могут реально съедать часть процессорного времени в достаточном количестве, чтобы показывать неожиданные результаты.

Если скомпилировать какой-нить простой цикл с оптимизацией -O2 и тем более -O3, то нередко компилятор, например, заменит N итераций цикла на N/4 или N/8, в каждой из которых будет по 4 или 8 операций. Потому что в силу особенностей современных процессоров это может оказаться вычислительно эффективнее. Программист бы никогда так не написал, а компилятор улучшает его код и делает быстрее. Компиляторы в языки более высокого уровня и даже интерпретаторы байткода на таких мелочах иногда неожиданно выигрывают.

Надо вникать, что именно получилось на выходе в обоих случаях. Может, рекурсия в go уже не рекурсия, а хвостовая рекурсия? Или вместо far call используются near call, которые быстрее? Или даже как таковых вызовов нет? А может там лишнее время съедает динамическая линковка при запуске, которая в go меньше выражена (а при сборке в статический бинарник вообще отсутствует)?
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы