Поможет ли использование нескольких потоков с RandomAccessFile повысить производительность? - PullRequest
5 голосов
/ 23 июня 2009

Я работаю над проектом (database-ish), где данные хранятся в виде плоского файла. Для чтения / записи я использую класс RandomAccessFile. Получу ли я что-нибудь от многопоточности и предоставлю каждому потоку экземпляр каждого из RandomAccessFile, или один поток / экземпляр будет таким же быстрым? Есть ли разница в чтении / письме, поскольку вы можете создавать экземпляры, которые выполняют только чтение и не могут писать?

Ответы [ 7 ]

13 голосов
/ 10 августа 2009

Я сейчас сделал тест с кодом ниже (извините, это в cpp). Код читает текстовый файл размером 5 МБ с количеством потоков, переданных в качестве аргумента командной строки.

Результаты ясно показывают, что несколько потоков всегда ускоряют программу :

Обновление: Мне пришло в голову, что кеширование файлов здесь сыграет свою роль. Поэтому я сделал копии файла testdata, перезагрузил компьютер и использовал разные файлы для каждого запуска. Обновленные результаты ниже (старые в скобках). Вывод остается прежним.

Время выполнения в секундах

Машина A (Dual Quad Core XEON под управлением XP x64 с 4 дисками SAS 10k в RAID 5)

  • 1 Тема: 0,61 с (0,61 с)
  • 2 темы: 0,44 с (0,43 с)
  • 4 Темы: 0,31 с (0,28 с) (Самый быстрый)
  • 8 Темы: 0,53 с (0,63 с)

Machine B (двухъядерный ноутбук под управлением XP с одним фрагментированным 2,5-дюймовым диском)

  • 1 поток: 0,98 с (1,01 с)
  • 2 Темы: 0,67 с (0,61 с) (Самый быстрый)
  • 4 темы: 1,78 с (0,63 с)
  • 8 Темы: 2,06 с (0,80 с)

Исходный код (Windows):

// FileReadThreads.cpp : Defines the entry point for the console application.
//

#include "Windows.h"
#include "stdio.h"
#include "conio.h"
#include <sys\timeb.h>
#include <io.h>    

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int threadCount = 1;
char *fileName = 0;
int fileSize = 0;
double  GetSecs(void);

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

DWORD WINAPI FileReadThreadEntry(LPVOID lpThreadParameter)

{   char tx[255];

    int index = (int)lpThreadParameter;
    FILE *file = fopen(fileName, "rt");

    int start = (fileSize / threadCount) * index;
    int end   = (fileSize / threadCount) * (index + 1);

    fseek(file, start, SEEK_SET);

    printf("THREAD %4d started: Bytes %d-%d\n", GetCurrentThreadId(), start, end);


    for(int i = 0;; i++)
    {
        if(! fgets(tx, sizeof(tx), file))
            break;
        if(ftell(file) >= end)
            break;
    }
    fclose(file);

    printf("THREAD %4d done\n", GetCurrentThreadId());

    return 0;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////



int main(int argc, char* argv[])
{
    if(argc <= 1)
    {
        printf("Usage:  <InputFile> <threadCount>\n");
        exit(-1);
    }

    if(argc > 2)
        threadCount = atoi(argv[2]);

    fileName = argv[1];
    FILE *file = fopen(fileName, "rt");
    if(! file)
    {
        printf("Unable to open %s\n", argv[1]);
        exit(-1);
    }

    fseek(file, 0, SEEK_END);
    fileSize = ftell(file);
    fclose(file);


    printf("Starting to read file %s with %d threads\n", fileName, threadCount);
    ///////////////////////////////////////////////////////////////////////////
    // Start threads
    ///////////////////////////////////////////////////////////////////////////
    double start = GetSecs();

    HANDLE mWorkThread[255];        

    for(int i = 0; i < threadCount; i++)
    {
        mWorkThread[i] = CreateThread(
                  NULL,
                  0,
                  FileReadThreadEntry,
                  (LPVOID) i,
                  0, 
                  NULL);
    }
    WaitForMultipleObjects(threadCount, mWorkThread, TRUE, INFINITE);

    printf("Runtime %.2f Secs\nDone\n", (GetSecs() - start) / 1000.);
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

double  GetSecs(void)

{
        struct timeb timebuffer;
        ftime(&timebuffer);
        return (double)timebuffer.millitm + 
              ((double)timebuffer.time * 1000.) - // Timezone needed for DbfGetToday
              ((double)timebuffer.timezone * 60. * 1000.);
}
9 голосов
/ 26 июня 2009

По моему опыту разработки на C ++ ответ таков: да, использование нескольких потоков может повысить производительность при чтении файлов. Это относится как к последовательному, так и к последовательному доступу. Я доказывал это не раз, хотя я всегда обнаруживал, что настоящие узкие места находятся где-то еще.

Причина в том, что для доступа к диску поток будет приостановлен до завершения операции с диском. Но большинство дисков сегодня поддерживают Native Command Queuing см. (SAS) или Segate (SATA) (как и большинство систем RAID) и поэтому не должны обрабатывать запросы в том порядке, сделать их.

Таким образом, если вы прочитали 4 последовательных чанка файла, ваша программа должна будет дождаться первого чанка, а затем запросить второй и так далее. Если вы запросите 4 блока с 4 потоками, они могут быть возвращены сразу. Этот вид оптимизации имеет ограничения, но он работает (хотя у меня есть опыт только с C ++ здесь). Я измерял, что несколько потоков могут улучшить производительность последовательного чтения более чем на 100%.

3 голосов
/ 23 июня 2009

Глядя на JavaDoc на RandomAccessFile, сам класс не синхронизируется. Похоже, что вы можете использовать синхронный режим для операций чтения и записи. Если вы не используете синхронизированный режим, вам придется самостоятельно управлять блокировками при чтении и записи, что далеко не тривиально. То же самое будет верно для прямого java.io при использовании нескольких потоков.

Если это вообще возможно, вы, вероятно, захотите взглянуть на использование базы данных, поскольку база данных обеспечивает этот вид многопоточной абстракции. Вы также можете посмотреть, какие опции системного журнала доступны для Java или даже для log4j.

3 голосов
/ 23 июня 2009

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

Маленькие операции RandomAccessFile ужасно медленны.

Для максимальной производительности вам, вероятно, лучше перейти прямо к java.nio, хотя я бы посоветовал заставить что-то работать, прежде чем заставить работать быстро. OTOH, имейте в виду производительность.

1 голос
/ 24 апреля 2015

Я удивлен, что каждый ответ говорит о производительности , но никто не отличает латентность от пропускной способности , тогда как оба являются характеристиками производительности. Хотя вы можете увеличить пропускную способность, используя несколько потоков, как показывает @ RED ​​SOFT ADAIR , вы компенсируете задержку, особенно в случае Native Command Sequencing.

1 голос
/ 23 июня 2009

Существует возможность сохранить в памяти ваш плоский файл с помощью NIO. В этом случае диспетчер памяти ОС становится ответственным за перемещение разделов файла. Вы также можете применять региональные блокировки для писателей.

0 голосов
/ 24 июня 2009

Довольно распространенный вопрос. В основном использование нескольких потоков не заставит ваш жесткий диск работать быстрее. Вместо этого выполнение одновременного запроса может замедлить его выполнение.

Дисковые подсистемы, esp IDE, EIDE, SATA, предназначены для последовательного чтения / записи.

...