我正在开发一个(database-ish)项目,其中数据存储在一个平面文件中。对于读/写我正在使用RandomAccessFile
类。我是否可以从多线程中获得任何东西,并为每个线程提供RandomAccessFile
的每个实例,或者一个线程/实例是否同样快?读/写是否有任何区别,因为你可以创建只进行读取但不能写的实例?
答案 0 :(得分:13)
我现在使用下面的代码做了一个基准测试(对不起,它在cpp中)。 该代码读取一个5 MB的文本文件,其中包含许多作为命令行参数传递的线程。
结果清楚地表明多线程总是加速程序:
更新:我想到,文件缓存在这里起着相当重要的作用。所以我制作了testdata文件的副本,重新启动并为每次运行使用了不同的文件。下面更新了结果(括号中的旧结果)。结论保持不变。
以秒为单位的运行时
机器A(运行XP x64的双四核XEON,在RAID 5中配备4个10k SAS驱动器)
机器B(运行XP的双核笔记本电脑,带有一个零碎的2.5英寸驱动器)
源代码(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,设计为按顺序读/写速度最快。