LINUX.ORG.RU
ФорумTalks

Вау, джава не медленнее Си на обработке больших текстовых массивов


0

3

Только что портил некоторые тулзы, обрабатывающие генетическую информацию с ANSI C на джаву, строка-в-строку.
И был очень удивлён, что разница - в пределах ошибки.
Тулзы типа транспонируют матрицы, ну просто крутят данные, а данных могут быть многие гигабайты. Только одна строка - порядка миллиона аллелей, а их может быть много.
Дык вот, на Си, скажем, миллион рекордов обрабатывается в 1.5-1.8 секунд. На джаве - 2.5 секунд вместе с загрузкой виртуальной машины, но саму строку обрабатывает 1.8 секунд (колеблется в приделах 20% в моём ленивом семпле из 5 запусков). Т.е. когда количество строк будет очень большим - загрузкой VM можно пренебречь, а данные колошматят как Си (gcc -O2) и джава - с одинаковой скоростью.

Детали: маллоками не пользуюсь, всё делаю на стеке. В джаве тоже всё в стиле Си (немногие классы - как сишные структуры с публичным доступом - на частые операции, в основном - функции). Никакого юникода (его в тех форматах не предвидится), поэтому читаю асками, как intы и кастю в char под конец, только что нужно. Т.е. оптимизированнее некуда.

Этак можно на Си забить для подобных задач (я давно тестил одну и ту же большую экстракцию данных из базы - разницы не заметил, но там ясно: чистое ожидание IO, в базах на Си давно забил. Потом тестил хэши, работа с большимы хешами также в джаве оказалась даже быстрее). А тут такое. С большими локальными файлами и их обработке. Однако...

Вот и чудненько. Мне в целом Java нравится, но порой по оформлению она никуда не вписывается.

spoilt ★★★
()
Ответ на: комментарий от robot12

> Где тэг [жж] ???

Кстати, хотел на ЖЖ выложить статейку с замерами. Лень. Поэтому здесь, в толксах.

siberean
() автор топика
Ответ на: комментарий от Obey-Kun

>> приделах

facepalm


уели. (я с 92 года в России не живу, но всё равно каюсь. Однако, если бы Вы почитали среднего русскоязычного ребёнка - если вообще он писать умеет - то уже одно то, что Вы поймёте хоть что-то - будет большим достижением, так что помилостивее будьте, помилостевее с хоть какими-то носителями, не миллиарды же их, в конце-то концов)

siberean
() автор топика

Все верно, и на си можно писать говнокод, который по скорости будет равен говнокоду на яве.

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

> Все верно, и на си можно писать говнокод, который по скорости будет равен говнокоду на яве.

скажу Вам, о гуру, спасибо - за советы по оптимизации например этого файла (только если действительно советы приведут к повышению перформанса):

http://ped2raw.svn.sourceforge.net/viewvc/ped2raw/ped2raw/main.c?revision=5&v...

на мой взгляд, писать более оптимально просто невозможно. Этот простой пример, есть гораздо более сложные, в других проектах - стейт-машина специально поддерживается для перформанса, где ничего никогда не читается 2 раза или ничего лишнего никогда не находится в памяти. Работает на моём Пентиуме 2/450Mhz/153М RAM 12-15 секунд (предыдущие цифры были даны для AMD64 3400, 1Гиг)

siberean
() автор топика
Ответ на: комментарий от siberean

Тот же код на джаве:

http://aisconvert.svn.sourceforge.net/viewvc/aisconvert/aisconvert/src/org/ai...

(перформанс которого, собственно, и сравнивался. код скопирован строка в строку, за редким исключением)

я почему так озаботился перформансом Си - это потому что собрался переписывать более сложные алгоритмы на Си, например вот этот:

http://aisconvert.svn.sourceforge.net/viewvc/aisconvert/aisconvert/src/org/ai...

А тут такое... (что разницы на больших текстовых данных - в общем то и нет)...

siberean
() автор топика
Ответ на: комментарий от siberean

> на мой взгляд, писать более оптимально просто невозможно.

ну да, при парсировании разделенного табами текста из файла использовать strtok, strcpy, fgets - это супер-пупер оптимально, ага

aho
()
Ответ на: комментарий от siberean

> А тут такое... (что разницы на больших текстовых данных - в общем то и нет)...

а вы пишите на С как на С, а не как на Java - и все будет хоккей

aho
()

>Только что портил некоторые тулзы, обрабатывающие генетическую информацию с ANSI C на джаву

портил


более точного описания и не подберешь :)

nu11 ★★★★★
()

Она и на вычислениях не сильно сливает.

elverion
()

а теперь 2 вопроса:
1) насколько хорошо распараллеливается алгоритм? Сколько потоков используется сейчас?
2) сколько памяти жрет старый и новый вариант на этих самых «гигабайтах данных»?

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

nu11 ★★★★★
()

На си вообще обработка строк через жопу сделана. А что на числодробилках жаба идет вровень с си не новость. На http://shootout.alioth.debian.org/u32/benchmark.php?test=all&lang=java&lang2=gcc местами жаба в полтора раза быстрее си (особенно на всяких выравниваниях)

DNA_Seq ★★☆☆☆
()
Ответ на: комментарий от nu11

>более точного описания и не подберешь :)

Те кто в теме поймут. Правда они обычно прекрасно в курсе что подобное лучше на перле писать

DNA_Seq ★★☆☆☆
()

> Этак можно на Си забить для подобных задач

Теперь запусти в 2-3 параллельных процесса на яве и столько же на С и пересчитай ресурсы. В современных реализациях в итоге выполняется такой же скомпилированный код. Расплата идёт ресурсами.

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

iBliss
()

> Потом тестил хэши, работа с большимы хешами также в джаве оказалась даже быстрее).

В каком смысле «работа с хешами»??? Чем хеши отличаются от других данных?

segfault ★★★★★
()

Если программа несложная, было бы интересно ещё посмотреть сравнение с производительностью языка D (компилятор на выбор: ldc или dmd, но лучше dmd). Писать на нём приятнее, чем на C и Java.

eveel ★★
()
Ответ на: комментарий от aho

> при парсировании разделенного табами текста из файла использовать strtok, strcpy, fgets - это супер-пупер оптимально, ага

А что не так?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> А что не так?

a) вычитка файла через getc - медленнее чем читать сразу в буфер ( fgets - это еще я случайно увидел, там getc везде )

б) просто вернуть указатель - это быстрее чем делать strcpy

aho
()
Ответ на: комментарий от siberean

> на мой взгляд, писать более оптимально просто невозможно.

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

AndreyKl ★★★★★
()
Ответ на: комментарий от aho

> a) вычитка файла через getc - медленнее чем читать сразу в буфер ( fgets - это еще я случайно увидел, там getc везде )

getc тоже читает из буфера.

б) просто вернуть указатель - это быстрее чем делать strcpy

Может быть, но на горячем кэше вряд ли разница велика...

tailgunner ★★★★★
()
Ответ на: комментарий от eveel

Таких языков чтобы приятнее было код писать еще куча. Но все не то, несерьезно как-то

vertexua ★★★★★
()
Ответ на: комментарий от tailgunner

> getc тоже читает из буфера.

тоже, вот только сделать инкремент указателю на char* «дешевле» чем вызвать функцию

Может быть, но на горячем кэше вряд ли разница велика...


да - может всего-лишь в несколько раз будет быстрее

П.С. вот кстати про strtok:

http://paste.ubuntu.com/601284/

в три раза практически разница, это про «оптимизированнее некуда»

aho
()
Ответ на: комментарий от aho

> в три раза практически разница, это про «оптимизированнее некуда»

В коде 2 вызова strok, причем 1 из них вне цикла. Ты правда думаешь, что увеличение производительности strtok в 3 раза даст 3-кратный выигрыш в производительности? Да хоть заметный выигрыш?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> В коде 2 вызова strok, причем 1 из них вне цикла.

а за время работы программы - тоже будет один вызов?

Ты правда думаешь, что увеличение производительности strtok в 3 раза даст 3-кратный выигрыш в производительности?


нет конечно, а разве я говорил, что strtok - узкое место? если программу в целом «подтянуть» - она и работать в целом будет быстрее

aho
()
Ответ на: комментарий от aho

В коде 2 вызова strok, причем 1 из них вне цикла.

а за время работы программы - тоже будет один вызов?

Нет. Но будет и куча других вызовов. И где там тратится время - без профайлера не скажешь.

если программу в целом «подтянуть» - она и работать в целом будет быстрее

Я как раз в этом и сомневаюсь. По-моему, типичная IO-bound прога. Небось, еще и обязана уметь файлы в несколько гиг.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> И где там тратится время - без профайлера не скажешь.

согласен

По-моему, типичная IO-bound прога. Небось, еще и обязана уметь файлы в несколько гиг.


тоже согласен, и задача программиста - ее такой и оставить, а не налепить миллиарды вызовов getc и т.д.

aho
()

Вполне возможно, что вы исследуете I/O системы, а не скорость реализации
на ЯП.

Т.е. оптимизированнее некуда.


И что, даже mmap используется ? // ъ код не смотрел

elipse ★★★
()
Ответ на: комментарий от aho

>> По-моему, типичная IO-bound прога. Небось, еще и обязана уметь файлы в несколько гиг.

тоже согласен, и задача программиста - ее такой и оставить, а не налепить миллиарды вызовов getc и т.д.

Задача прогера в таком случае - сделать программу без изъебов. Это значит - не писать свою буферизацию и токенизатор.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Задача прогера в таком случае - сделать программу без изъебов.
Это значит - не писать свою буферизацию

хорошо, показываю на пальцах:

$ echo "1.c" && cat ./1.c && echo "2.c" && cat ./2.c && gcc -O2 ./1.c && time ./a.out && gcc -O2 ./2.c && time ./a.out 
1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	FILE* f = fopen( "/home/igor/koffice-2.3.1.tar.bz2", "rb" );

	int c = 0;
	int s = 0;

	while( !feof( f ) )
		s += (char) getc( f );

	fclose( f );

	return 0;
}

2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	FILE* f = fopen( "/home/igor/koffice-2.3.1.tar.bz2", "rb" );

	char* p = malloc( 200000000 );
	int   s = 0;

	char* end = p + fread( p, 1, 200000000, f );
	char* it = p;
	while( it < end ) s += *it++;
	
	free( p );

	return 0;
}


real	0m1.152s
user	0m1.132s
sys	0m0.016s

real	0m0.074s
user	0m0.000s
sys	0m0.072s

разница быдлокода «без изъебов» и «сложного» буфера хорошо видна?

aho
()
Ответ на: комментарий от aho

вот более правильный вариант:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 1024 * 1024

int main()
{
	FILE* f = fopen( "/home/igor/koffice-2.3.1.tar.bz2", "rb" );

	char   p[ BUF_SIZE ];
	int    s = 0;
	size_t c;

	do
	{
		c = fread( p, 1, BUF_SIZE, f );

		char* end = p + c;
		char* it = p;
		while( it < end ) s += *it++;
	}
	while( c == BUF_SIZE );

	return 0;
}

выполняется уже за 0m0.021s

aho
()
Ответ на: комментарий от aho

> разница быдлокода «без изъебов» и «сложного» буфера хорошо видна?

Нет :D Ты отмонтировал ФС между тестами? %)

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Нет :D Ты отмонтировал ФС между тестами? %)

там файл в 60Мб - читается около секунды на «свежей» ФС, код «„без изъебов“ дольше на нем сумму считает, не говоря уж про пропарсить и обработать

aho
()
Ответ на: комментарий от aho

Большое спасибо всем. Очень познавательно.
С буферами я не заморачивался, а вижу зря. Прежде всего - потому что строками читать конечно же нельзя (пока в строке под миллион аллелей а в будущем может быть до 2х и может и больше), во вторых потому что думал что getc всё равно берёт из буфера, а оверхеды вызовов из функций инлайнятся. Получается, что циклический буфер малого размера должен сильно помочь.

Но fread( p, 1, 200000000, f ), конечно, напрягает (на некоторых моих машинах и памяти-то такой нет). Хотелось бы не отжирать память.

Попробую наверное взять что-то типа мегабайта. Но конечно, читаемость кода будет несколько хуже. Так как вся текущая функциональность останется, но лишние сущности введутся.

siberean
() автор топика
Ответ на: комментарий от aho


Нет :D Ты отмонтировал ФС между тестами? %)


там файл в 60Мб - читается около секунды на «свежей» ФС


Ну, мне-то нетрудно проверить честно, с отмонтированием. «a» - это твой вариант 1, «b» - «более правильный вариант»

time ./a /media/PENDRIVE/debian.img

real 1m10.208s
user 0m47.807s
sys 0m1.564s

time ./b /media/PENDRIVE/debian.img

real 1m10.504s
user 0m6.640s
sys 0m1.456s

Как видишь, разницы в реальном времени тупо нет. Разница в user-времени - за счет того, что твой «более правильный» вариант вообще никак не использует считанные данные.

Ах да, размер debian.img - 2Г.

tailgunner ★★★★★
()
Ответ на: комментарий от memnek

> он страшный и не мимикрирует под нужный тулкит

Мне по барабану тулкит. TCL/Tk ещё страшнее. И даже тот работает и очень даже портабельно.
Я когда-то делал проги для счёта (регрессии, планирование), которую рассылали всем госпиталям, или прогу, которая делает какой-нить процессинг.
В одном случае на TCL/Tk (под ней - сишная прога), в другом - на джаве. Ни в том ни в другом случае - ни у одного из клиентов проблем с гуёй не возникало.
Все файлы выбирались через гую очень хорошо, клиенты были довольны. А в случае джавы - зато ещё всякие прогресс-бары (работающие в параллельных тредах с процессингом) и файло-селекторы, менюшки были, совсем хорошо. Свинг несравнимо богаче тикля.
Не буду говорить, сколько накушаетесь, если будете шиппить что-то дотнеттовское, со многими версиями дот нетов и многими версиями виндовс (а если у юзера вин2к или вообще вин98?).

Я же для пользователей оффтопика делал один win32 дистрибутив, положив jre внутрь, всё зипповал, и юзер просто раззиповывал и шлёпал на .bat файл.

Как здесь:
http://sourceforge.net/projects/aisconvert/files/aisconvert-1.11-bin-with-jre...
(на работе я использовал тот же подход, что и в aisconvert, но код конечно же был совершенно другой).

На чём предлагаете Вы?

siberean
() автор топика
Ответ на: комментарий от tailgunner

спасибо
теперь вспомнил (с универа я его не припомню, т.е. так и не использовал)

siberean
() автор топика
Ответ на: комментарий от tailgunner

> Ну, мне-то нетрудно проверить честно, с отмонтированием

проверил у себя - действительно разница действительно гораздо меньше на больших некэшированных файлах, без буфера - 38сек, с буфером - 33сек, 2.2Гб файл

aho
()
Ответ на: комментарий от DNA_Seq

На си вообще обработка строк через жопу сделана. А что на числодробилках жаба идет вровень с си не новость.

местами жаба в полтора раза быстрее си

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

sergej ★★★★★
()
Ответ на: комментарий от sergej

>Но ведь это говорит лишь о том, что код на си написан не очень хорошо

это может говорить о неоптипамльности:
1) кода
2) библиотек
3) компилятора

Либы и компилятор тоже переписывать будешь?

DNA_Seq ★★☆☆☆
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.