Почему Java читает большой файл быстрее, чем С++?
У меня есть файл размером 2 GB (iputfile.txt
), в котором каждая строка в файле является словом, как:
apple
red
beautiful
smell
spark
input
Мне нужно написать программу, чтобы читать каждое слово в файле и печатать количество слов. Я написал его с использованием Java и С++, но результат удивителен: Java работает в 2,3 раза быстрее, чем С++. Мой код выглядит следующим образом:
С++:
int main() {
struct timespec ts, te;
double cost;
clock_gettime(CLOCK_REALTIME, &ts);
ifstream fin("inputfile.txt");
string word;
int count = 0;
while(fin >> word) {
count++;
}
cout << count << endl;
clock_gettime(CLOCK_REALTIME, &te);
cost = te.tv_sec - ts.tv_sec + (double)(te.tv_nsec-ts.tv_nsec)/NANO;
printf("Run time: %-15.10f s\n", cost);
return 0;
}
Вывод:
5e+08
Run time: 69.311 s
Java:
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
FileReader reader = new FileReader("inputfile.txt");
BufferedReader br = new BufferedReader(reader);
String str = null;
int count = 0;
while((str = br.readLine()) != null) {
count++;
}
System.out.println(count);
long endTime = System.currentTimeMillis();
System.out.println("Run time : " + (endTime - startTime)/1000 + "s");
}
Вывод:
5.0E8
Run time: 29 s
Почему в этой ситуации Java быстрее, чем С++, и как повысить производительность С++?
Ответы
Ответ 1
Вы не сравниваете одно и то же. Программа Java читает строки, связанные с новой строкой, в то время как программа на С++ читает пробелы с пробелами "слова", что является небольшой дополнительной работой.
Попробуйте istream::getline
.
В дальнейшем
Вы также можете попробовать выполнить элементарное чтение для чтения массива байтов и просмотреть его для строк новой строки.
Еще позже
На моем старом ноутбуке Linux, jdk1.7.0_21 и don't-tell-me-it-old 4.3.3 берут примерно то же самое время, сравнивая с С++ getline. (Мы установили, что чтение слов происходит медленнее.) Между -O0 и -O2 нет большой разницы, что меня не удивляет, учитывая простоту кода в цикле.
Последнее примечание
Как я уже сказал, fin.read(buffer, LEN) с LEN = 1MB и использование memchr для сканирования для '\n' приводит к еще одному повышению скорости около 20%, что делает C (на данный момент нет С++) быстрее, чем Java.
Ответ 2
Существует ряд существенных различий в том, как
Языки I/O, все из которых могут изменить ситуацию, в одном направлении
или другой.
Возможно, первый (и самый важный) вопрос: каким образом
данные, закодированные в текстовом файле. Если это однобайтовые символы
(ISO 8859-1 или UTF-8), затем Java должен преобразовать его в UTF-16
перед обработкой; в зависимости от языка, С++ может (или не может)
также конвертировать или выполнять некоторую дополнительную проверку.
Как было указано (частично, по крайней мере), в С++, >>
использует
специфический для локали isspace
, getline
будет просто сравниваться для
'\n'
, что, вероятно, быстрее. (Типичные реализации
isspace
будет использовать растровое изображение, что означает дополнительную память
доступ для каждого символа.)
Уровни оптимизации и конкретные реализации библиотек могут
также меняются. Это не необычно в С++ для одной библиотеки
реализация будет в 2 или 3 раза быстрее, чем другая.
Наконец, самое значительное различие: С++ отличает
между текстовыми файлами и двоичными файлами. Вы открыли файл в
текстовый режим; это означает, что он будет "предварительно обработан" на
самый низкий уровень, прежде чем даже операторы добычи видят это. Эта
зависит от платформы: для платформ Unix "предварительная обработка"
- нет-op; в Windows он преобразует пары CRLF в '\n'
,
которые окажут определенное влияние на производительность. Если я вспомню
правильно (я не использовал Java в течение нескольких лет), Java ожидает
функции более высокого уровня, чтобы справиться с этим, поэтому такие функции, как
readLine
будет немного сложнее. Просто гадать
здесь, но я подозреваю, что дополнительная логика выше
уровень затрат меньше во время выполнения, чем предварительная обработка буфера на
Нижний уровень. (Если вы тестируете под Windows, вы можете
эксперимент с открытием файла в двоичном режиме на С++. Эта
не должны влиять на поведение программы, когда
вы используете >>
; любой дополнительный CR будет считаться пробелом. С
getline
, вам нужно будет добавить логику для удаления любых завершающих
'\r'
к вашему коду.)
Ответ 3
Я подозреваю, что основное отличие состоит в том, что java.io.BufferedReader
работает лучше, чем std::ifstream
, потому что он буферизует, а ifsteam - нет. BufferedReader заранее считывает большие фрагменты файла и передает их в вашу программу из ОЗУ при вызове readLine()
, в то время как std:: ifstream только считывает несколько байтов за раз, когда вы запрашиваете его, вызывая >>
-оператором.
Последовательный доступ больших объемов данных с жесткого диска обычно намного быстрее, чем доступ к множеству небольших блоков по одному.
Более справедливым сравнением было бы сравнение std:: ifstream с небуферизованным java.io.FileReader.
Ответ 4
Я не эксперт в С++, но у вас есть как минимум следующее, чтобы повлиять на производительность:
- Кэширование уровня ОС для файла
- Для Java вы используете буферизованный читатель и размер буфера по умолчанию для страницы или что-то еще. Я не уверен, как это делают потоки С++.
- Так как файл настолько велик, что JIT, вероятно, будет загружен, и он, вероятно, скомпилирует код байта Java лучше, чем если бы вы не включили оптимизацию для своего компилятора С++.
Поскольку I/O стоимость - вот основная стоимость здесь, я думаю, 1 и 2 являются основными причинами.
Ответ 5
Я бы также попытался использовать mmap вместо стандартного чтения/записи файла. Это должно позволить вашей ОС обрабатывать чтение и запись, пока ваше приложение связано только с данными.
Нет ситуации, когда С++ не может быть быстрее Java, но иногда это требует много работы от очень талантливых людей. Но я не думаю, что это должно быть слишком сложно победить, поскольку это простая задача.
mmap для Windows описывается в Сопоставление файлов (MSDN).