компьютеры

Си опять не обогнал Джаву

Попробовал снова устроить соревнование двух языков программирования. В качестве подопытного алгоритма в этот раз у нас очень древний (200 лет до нашей эры!) способ нахождения всех простых чисел до некоторого целого числа n, называемый Решето Эратосфена. Берём массив чисел и начинаем вычёркивать составные числа: начиная с 2 и кончая n, вычёркиваем в массиве все числа, являющиеся кратными текущему числу. Например, если надо найти все простые числа до 10, начиная с 2 мы зачеркнём 4, 6, 8. Потом зачеркнём всё кратное 3, т.е. 6, 9. И так далее. В результате останутся только простые числа.

Я знаю, что 6 уже было зачёркнуто как кратное 2. Но вставление лишнего if в самый центр цикла (т.е. в то, что выполняется максимальное количество раз) сильно замедляет работу алгоритма. В машинном времени дешевле поставить 0 в нужном месте в памяти, чем проверять лишний раз.

Сложность алгоритма O(n log(log(n))).

Я находил все простые числа до 200 000 000 (больше не получалось, выскакивало переполнение кучи). После чего распечатывал первые 10 и последние 10. Делал я это во-первых, с целью проверить правильность работы алгоритма, а во-вторых компилятор нынче сильно умный, зараза, пошёл. Он мог увидеть, что у меня все эти данные потом в программе не используются, и тупо пропустить выполнение. Уже видел такое не раз и не два.

[Сишный код]
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{

int limit = 200000000;

static int primes[200000000];

for (int i = 2; i < limit; i++)
{
primes[i] = 1;
}

clock_t startTime = clock();

for (int i = 2; i < limit; i++)
{
if(primes[i] == 1)
{
for (int j = 2; j < (double)limit / i; j++)
{
primes[j * i] = 0;
}

}
}

clock_t endTime = clock();

double elapsed_time = (double)(endTime - startTime) / CLOCKS_PER_SEC;

printf("Elapsed time, seconds: %f\n", elapsed_time);

int maxToPrint = 10;
int printed = 0;

for (int i = 2; i < limit; i++)
{
if (primes[i] == 1)
{
printf("%i\n", i);
printed++;
}

if (printed == maxToPrint)
{
break;
}

}

printed = 0;

for (int i = limit - 1; i > 0; i--)
{
if (primes[i] == 1)
{
printf("%i\n", i);
printed++;
}

if (printed == maxToPrint)
{
break;
}
}

}


[Джавовский код]
public class primes
{

public static void main(String[] args)
{

int limit = 200000000;

int primes[] = new int[200000000];

for (int i = 2; i < limit; i++)
{
primes[i] = 1;
}

//init done
long startTime = System.nanoTime();

for (int i = 2; i < limit; i++)
{
if(primes[i] == 1)
{
for (int j = 2; j < (double)limit / i; j++)
{
primes[j * i] = 0;
}

}
}

long endTime = System.nanoTime();
System.out.println("Elapsed time, seconds: " + (endTime - startTime) / 1000000000.0);

int maxToPrint = 10;
int printed = 0;

for (int i = 2; i < limit; i++)
{
if (primes[i] == 1)
{
System.out.println(i);
printed++;
}

if (printed == maxToPrint)
{
break;
}

}

printed = 0;

for (int i = limit - 1; i > 0; i--)
{
if (primes[i] == 1)
{
System.out.println(i);
printed++;
}

if (printed == maxToPrint)
{
break;
}
}

}

}


А теперь -- результаты.

Под виртуалкой Ubuntu Сишная версия слегка выигрывала, выполняясь в среднем за 6.51 секунд против 6.71 секунд у Джава-варианта.
Под баш-оболочкой в Windows 10 с большим отрывом выиграла уже Джава, выполняясь за 4.75 секунд вместо 6.4 у Сишной версии.
И, наконец, я скомпилировал Сишный код под Windows, поставив дистрибутив LLVM (clang), и запустил всё просто в командной строке. Скорость выполнения обоих программ оказалась одинаковой -- от 4.69 до 4.75 секунд для обоих вариантов, разница, если она и была, составляла какие-то доли процента -- статистически незначимо.

Си компилировал везде clang со степенью оптимизации 3. В этот раз gcc показал себя не так сильно отстающим от clang (но всё же отстающим), как в прошлый раз. Java -- JDK 9, экспорт в jar из Eclipse.

Итого! Как мы видим, скорость выполнения данных программ больше зависит от того, где и как программа запускается, чем от языка программирования.

Засим -- извольте откланяться, на тему "Си быстрее Джавы" я могу предъявить уже два теста, где Си нихера не быстрее Джавы. Если кто-то продолжит упорствовать, пусть пишет свои тесты, выкладывает код в открытый доступ, чтобы можно было устроить независимую перепроверку. А я -- за то, чтобы брать ПРАВИЛЬНЫЙ инструмент под задачу, а не размахивать флагом "Лучший язык программирования -- Паскаль". Драйверы низкоуровневые или там ядро ОС -- пишите на голых Сях. А крупный корпоративный проект на них вы писать УСРЁТЕСЬ, а на Джаве -- нормально. Что лучше -- молоток или пила? Отвёртка или кувалда? Да ни то ни другое. Они разные и для разного.
Ну так ты продолжаешь тестировать как низкоуровневые библиотеки языка справляются со своей работой - организуют циклы, и проходят проверки на равенство. Так как эта часть отлаживается долгое время и опускается весьма близко к железу которое и исполняет код, то не удивительно что скорость выполнения одинакова. Я лет двадцать назад пробовал найти в вижуал бейсике наиболее быстрый способ отсортировать несколько тысяч строк, полученные из базы данных "на лету" Самым быстрым способом было сразу получить эти строки из базы сортированными. А вот дальше самым быстрым способом оказалось не грузить эти строки в аррей и сортировать. А одной командой загрузить весь рекордсет в дропдаун контрол и второй командой заставить его отсортировать.
Естественно, работа низкоуровненвого контрола не могла проиграть любому итерационному процессу внутри бейсика. Так же примерно и у тебя. Заставь джаву подгружать данные в обьекты, резервировать и освобождать память под них, собирать мусор и она явно станет "не торт". Хотя по сравнению с 10-15 годами ранее железо сильно продвинулось, и не так уж оно тормозит сейчас в самом деле

> Заставь джаву подгружать данные в обьекты, резервировать и освобождать память под них, собирать мусор и она явно станет "не торт".

Вот кстати создание новых объектов в Джаве заметно быстрее, чем на Сях, работает.

Собирание мусора vs. free() -- уже не уверен. Смотреть надо.

Но полагаю, что сборщик мусора -- это как раз та вещь, которую отлаживали (и продолжают отлаживать) до посинения.
Тут есть еще нюанс :) что сборщик мусора в Джаве как раз написан и исполняется на Си, что делает сравнение несколько менее осмысленным. Что есть еще одно достоинство Джавы, кмк, что если надо - используется native code, до которого пользователям Джавы дела быть не должно, но он, как тот суслик, там есть.
> Вот кстати создание новых объектов в Джаве заметно быстрее, чем на Сях, работает.

практически у всех memory managed langs managed heap - не дефрагментированная, отсюда и выигрыш
Что до собирания мусора - конечно, это будет дольше, при любых раскладах, за счет прохода по графу обьктов в поисках неиспользуемых, тут ничего не поделаешь
жава ужасна, древний язык, волокущий за собой тонны легаси, и в силу этого практически не развивающийся
Да и си тоже, чисто загончик для embedded
Да ладно, легаси они регулярно отстреливают =)

И вообще новые мажорные релизы JRE обновляются как бы не чаще, чем енти ваши доднеты.
разве что совсем недавно они стали это делать

проблема в том, что из-за легаси и декларируемой обратной совместимостимости у них отвратительные generics, совершенно чюдовищные лямбды, ну и как следствие - их попытки ввести монады обернулись пшиком, по сути

Плюс, жава после сишарпа - это какое-то просто невероятное к-во boiler plate code. Постоянно страдаю!!
Когда пишешь код, такое впечатление, что пишешь обьяснения для умственно отсталого - надо все тсчательно отисывать, т.к. компилятор сам не догадается!
Вот дженерик там косой, да.

Про лямбды и монады ничего не скажу -- не знаю. Это, вроде, больше в парадигму функционального программирования заезд?

> Плюс, жава после сишарпа - это какое-то просто невероятное к-во boiler plate code. Постоянно страдаю!!

Кхе... это ты ещё на Питоне не писал. После Питона даже сишарп кажется ассемблером или около того =)
дженерик не то что косой, у него, например, co- & contra-variance сделаны нагляднее. Но! Проблема в том, что это не настоящий дженерик, под капюшоном он все равно Object , и от этого много вещей с ним сделать нельзя

Функциональщина, да. Сильно упрощает многие аспекты

Ну, это ты сишарп не очень хорошо знаешь;) Там можно очень лаконично писать, особенно в 7-м

Ну и плюс оракл. No one wants to be Larry's bitch
> Ну, это ты сишарп не очень хорошо знаешь;) Там можно очень лаконично писать, особенно в 7-м

Ну может оно и так, конечно.

Но вот программа, читающая wav файл, производящая fft на полученном массиве, выделяющая фундаментальную частоту, вычисляющая SNR, и графиццки выводящая график шумов по частотам на Питоне у меня заняла всего 40 строчек кода.

Я б вот даже не знаю, сколько бы это заняло на Джаве или на Шарпах.
это ты про фреймворк говоришь, не про собственно язык:)