使用System.Threading和Visual Studio C#Express托管过程,多线程代码执行速度比线程数慢
我有一个非常简单的程序来计算字符串中的字符.整数threadnum
设置线程数,然后将数据按threadnum
划分为每个要处理的线程的大块.
I have a very simple program counting the characters in a string. An integer threadnum
sets the number of threads and divides the data by threadnum
accordingly into chunks for each thread to process.
每个线程都会增加共享字典中包含的值,从而建立字符历史图.
Each thread increments the values contained in a shared dictionary, building a character historgram.
private Dictionary<UInt32, int> dict = new Dictionary<UInt32, int>();
- 为了等待所有线程完成并继续执行主进程,我调用
Thread.Join
- 最初,我为每个线程都有一个本地字典,之后将其合并,但是共享字典可以很好地工作,而无需锁定.
- 在方法 BuildDictionary 中,没有引用被锁定,尽管锁定字典并不会显着影响线程执行时间.
- 每个线程都有时间,并且比较结果字典.
- 无论单线程还是多线程,字典的内容都是相同的-应为.
- 每个线程需要完成一个由线程数确定的分数-应为.
- In order to wait for all threads to finish and continue with the main process, I invoke
Thread.Join
- Initially I had a local dictionary for each thread which get merged afterwards, but a shared dictionary worked fine, without locking.
- No references are locked in the method BuildDictionary, though locking the dictionary did not significantly impact thread-execution time.
- Each thread is timed, and the resulting dictionary compared.
- The dictionary content is the same regardless of a single or multiple threads - as it should be.
- Each thread takes a fraction determined by threadnum to complete - as it should be.
问题:
总时间大约是threadnum
的倍数,也就是说,执行时间增加了?
The total time is roughly a multiple of threadnum
, that is to say the execution time increases ?
(不幸的是,我目前无法运行C#Profiler.此外,我更喜欢C#3代码兼容性.)
(Unfortunately I cannot run a C# Profiler at the moment. Additionally I would prefer C# 3 code compatibility. )
其他人也可能会挣扎.可能是 VS 2010 Express Edition vshost 进程堆栈并计划了线程按顺序运行吗?
Others are likely struggling as well. It may be that the VS 2010 express edition vshost process stacks and schedules threads to be run sequentially?
最近发布了另一个MT性能问题:
Another MT-performance issue was posted recently posted here as "Visual Studio C# 2010 Express Debug running Faster than Release":
代码:
public int threadnum = 8;
Thread[] threads = new Thread[threadnum];
Stopwatch stpwtch = new Stopwatch();
stpwtch.Start();
for (var threadidx = 0; threadidx < threadnum; threadidx++)
{
threads[threadidx] = new Thread(BuildDictionary);
threads[threadidx].Start(threadidx);
threads[threadidx].Join(); //Blocks the calling thread, till thread completion
}
WriteLine("Total - time: {0} msec", stpwtch.ElapsedMilliseconds);
可以帮忙吗?
更新:
由于IDE调试器的众多钩子,随着线程数的增加,几乎呈线性的速度降低的奇怪行为似乎是一个工件.
It appears that the strange behavior of an almost linear slowdown with increasing thread-number is an artifact due to the numerous hooks of the IDE's Debugger.
在开发人员环境之外运行该过程,实际上在2个逻辑/物理核心计算机上确实获得了30%的速度提高.在调试过程中,我已经处于CPU利用率的高端,因此我怀疑在开发过程中通过其他空闲内核留有一些余地是明智的.
Running the process outside the developer environment, I actually do get a 30% speed increase on a 2 logical/physical core machine. During debugging I am already at the high end of CPU utilization, and hence I suspect it is wise to have some leeway during development through additional idle cores.
起初,我让每个线程都在其自己的本地数据块上进行计算,该数据块被锁定并写回到共享列表中,并在所有线程完成后进行汇总.
As initially, I let each thread compute on its own local data-chunk, which is locked and written back to a shared list and aggregated after all threads have finished.
结论:
请注意进程在其中运行的环境.
Be heedful of the environment the process is running in.
我们暂时可以将字典同步问题Tony the Lion在他的回答中提到,因为在您当前的实现中,您实际上并未并行运行任何东西!
We can put the dictionary synchronization issues Tony the Lion mentions in his answer aside for the moment, because in your current implementation you are in fact not running anything in parallel!
让我们看看您当前在循环中正在做什么:
Let's take a look at what you are currently doing in your loop:
- 启动线程.
- 等待以完成线程.
- 启动下一个线程.
- Start a thread.
- Wait for the thread to complete.
- Start the next thread.
换句话说,您不应在循环内调用Join
.
In other words, you should not be calling Join
inside the loop.
相反,您应该在执行操作时启动所有线程,但是要使用诸如AutoResetEvent
之类的单一构造来确定所有线程何时完成.
Instead, you should start all threads as you are doing, but use a singaling construct such as an AutoResetEvent
to determine when all threads have completed.
请参阅示例程序:
class Program
{
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
int numThreads = 5;
for (int i = 0; i < numThreads; i++)
{
new Thread(DoWork).Start(i);
}
for (int i = 0; i < numThreads; i++)
{
_waitHandle.WaitOne();
}
Console.WriteLine("All threads finished");
}
static void DoWork(object id)
{
Thread.Sleep(1000);
Console.WriteLine(String.Format("Thread {0} completed", (int)id));
_waitHandle.Set();
}
}
或者,如果您引用了可用的线程,那么也可以在第二个循环中调用Join
.
Alternatively you could just as well be calling Join
in the second loop if you have references to the threads available.
完成此操作后,您可以应该担心字典同步问题.
After you have done this you can and should worry about the dictionary synchronization problems.