使用带有RandomAccessFile的多个线程有助于提高性能吗?

时间:2009-06-23 14:52:25

标签: java performance multithreading file-io concurrency

我正在开发一个(database-ish)项目,其中数据存储在一个平面文件中。对于读/写我正在使用RandomAccessFile类。我是否可以从多线程中获得任何东西,并为每个线程提供RandomAccessFile的每个实例,或者一个线程/实例是否同样快?读/写是否有任何区别,因为你可以创建只进行读取但不能写的实例?

7 个答案:

答案 0 :(得分:13)

我现在使用下面的代码做了一个基准测试(对不起,它在cpp中)。 该代码读取一个5 MB的文本文件,其中包含许多作为命令行参数传递的线程。

结果清楚地表明多线程总是加速程序

更新:我想到,文件缓存在这里起着相当重要的作用。所以我制作了testdata文件的副本,重新启动并为每次运行使用了不同的文件。下面更新了结果(括号中的旧结果)。结论保持不变。

以秒为单位的运行时

机器A(运行XP x64的双四核XEON,在RAID 5中配备4个10k SAS驱动器)

  • 1个主题:0.61s(0.61s)
  • 2个主题:0.44s(0.43s)
  • 4个主题:0.31s(0.28s)(最快)
  • 8个线程:0.53s(0.63s)

机器B(运行XP的双核笔记本电脑,带有一个零碎的2.5英寸驱动器)

  • 1个主题:0.98s(1.01s)
  • 2个主题:0.67秒(0.61秒)(最快)
  • 4个主题:1.78s(0.63s)
  • 8个主题:2.06s(0.80s)

源代码(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.);
}

答案 1 :(得分:9)

根据我从C ++开发的经验,答案是:是的,使用多个线程可以在读取文件时提高性能。这适用于顺序和串行访问。我不止一次地证明了这一点,尽管我总是发现真正的瓶颈在其他地方。

原因是,对于磁盘访问,线程将暂停,直到磁盘操作完成。但是现在大多数磁盘都支持本机命令队列see (SAS)Segate (SATA)(以及大多数RAID系统),因此不必按照您生成的顺序处理请求。

因此,如果您按顺序读取4个文件块,则您的程序必须等待第一个块,然后您请求第二个块,然后请求第一个块。如果您请求包含4个线程的4个块,则可以一次性返回它们。这种优化有限,但它有效(虽然我在这里只有C ++的经验)。我测量了多个线程可以将顺序读取性能提高100%以上。

答案 2 :(得分:3)

RandomAccessFile是同步的,所以如果你共享一个实例,那么你只会有一个线程在一个运行。糟糕,RandomAccessFile未同步,并且共享线程之间并不完全安全。当您有多个线程访问相同的可变数据结构时,您将需要小心,特别是当涉及操作系统的变幻莫测时。

RandomAccessFile的小型操作非常缓慢。

为了获得最佳性能,你可能最好直接进入java.nio,尽管我建议在让它快速运行之前先做一些工作。 OTOH,记住表现。

答案 3 :(得分:3)

在RandomAccessFile上查看JavaDoc,类本身不同步。您似乎可以使用同步模式进行读写操作。如果您不使用同步模式,虽然您将不得不管理自己阅读和写作的锁定,这远非微不足道。使用多个线程时直接java.io也是如此。

如果可能的话,您可能希望查看使用数据库,因为数据库提供了这种多线程抽象。您还可以查看可用于Java甚至log4j的syslog选项。

答案 4 :(得分:1)

可以选择使用NIO对您的平面文件进行内存映射。在这种情况下,OS内存管理器负责移动文件的输入输出部分。您还可以为作者应用区域锁定。

答案 5 :(得分:1)

我很惊讶每个答案都谈到性能,但没有人将延迟吞吐量区分开来,而两者都是性能特征。虽然您可以获得使用多个线程的额外吞吐量,如@RED SOFT ADAIR has shown,但您可以权衡延迟,尤其是在本机命令排序的情况下。

答案 6 :(得分:0)

一个相当常见的问题。基本上使用多个线程不会让你的硬盘更快。相反,执行并发请求会使速度变慢。

磁盘子系统,特别是IDE,EIDE,SATA,设计为按顺序读/写速度最快。