多线程为什么跑的比单线程还要慢?

“多个人干活比一个人干活要快,多线程并行执行也比单线程要快”这是我学习编程长期以来的想法。然而在实际的开发过程中,并不是所有情况下都是这样。先看看下面的程序(点击下载):

image

ThreadTester是所有Tester的基类。所有的Tester都干的是同样一件事情,把counter增加到100000000,每次只能加1。

   1: public abstract class ThreadTester
   2:     {
   3:         public const long MAX_COUNTER_NUMBER = 100000000;
   4:  
   5:         private long _counter = 0;
   6:  
   7:         //获得计数
   8:         public virtual long GetCounter()
   9:         {
  10:             return this._counter;
  11:         }
  12:  
  13:         //增加计数器
  14:         protected virtual void IncreaseCounter()
  15:         {
  16:             this._counter += 1;
  17:         }
  18:  
  19:         //启动测试
  20:         public abstract void Start();
  21:  
  22:         //获得Counter从开始增加到现在的数字所耗的时间
  23:         public abstract long GetElapsedMillisecondsOfIncreaseCounter();
  24:  
  25:         //测试是否正在运行
  26:         public abstract bool IsTesterRunning();
  27:     }

SingleThreadTester是单线程计数。

   1: class SingleThreadTester : ThreadTester
   2:     {
   3:         private Stopwatch _aStopWatch = new Stopwatch();
   4:  
   5:         public override void Start()
   6:         {
   7:             _aStopWatch.Start();
   8:  
   9:             Thread aThread = new Thread(() => WorkInThread());
  10:             aThread.Start();
  11:         }
  12:  
  13:         public override long GetElapsedMillisecondsOfIncreaseCounter()
  14:         {
  15:             return this._aStopWatch.ElapsedMilliseconds;
  16:         }
  17:  
  18:         public override bool IsTesterRunning()
  19:         {
  20:             return _aStopWatch.IsRunning;
  21:         }
  22:  
  23:         private void WorkInThread()
  24:         {
  25:             while (true)
  26:             {
  27:                 if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  28:                 {
  29:                     _aStopWatch.Stop();
  30:                     break;
  31:                 }
  32:  
  33:                 this.IncreaseCounter();
  34:             }
  35:         }
  36:     }

TwoThreadSwitchTester是两个线程交替计数。

   1: class TwoThreadSwitchTester : ThreadTester
   2:     {
   3:         private Stopwatch _aStopWatch = new Stopwatch();
   4:         private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
   5:  
   6:         public override void Start()
   7:         {
   8:             _aStopWatch.Start();
   9:  
  10:             Thread aThread1 = new Thread(() => Work1InThread());
  11:             aThread1.Start();
  12:  
  13:             Thread aThread2 = new Thread(() => Work2InThread());
  14:             aThread2.Start();
  15:         }
  16:  
  17:         public override long GetElapsedMillisecondsOfIncreaseCounter()
  18:         {
  19:             return this._aStopWatch.ElapsedMilliseconds;
  20:         }
  21:  
  22:         public override bool IsTesterRunning()
  23:         {
  24:             return _aStopWatch.IsRunning;
  25:         }
  26:  
  27:         private void Work1InThread()
  28:         {
  29:             while (true)
  30:             {
  31:                 _autoResetEvent.WaitOne();
  32:                 
  33:                 this.IncreaseCounter();
  34:  
  35:                 if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  36:                 {
  37:                     _aStopWatch.Stop();
  38:                     break;
  39:                 }
  40:  
  41:                 _autoResetEvent.Set();
  42:             }
  43:         }
  44:  
  45:         private void Work2InThread()
  46:         {
  47:             while (true)
  48:             {
  49:                 _autoResetEvent.Set();
  50:                 _autoResetEvent.WaitOne();
  51:                 this.IncreaseCounter();
  52:  
  53:                 if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  54:                 {
  55:                     _aStopWatch.Stop();
  56:                     break;
  57:                 }
  58:             }
  59:         }
  60:     }

MultiThreadTester可以指定线程数,多个线程争抢计数。

   1: class MultiThreadTester : ThreadTester
   2:     {
   3:         private Stopwatch _aStopWatch = new Stopwatch();
   4:         private readonly int _threadCount = 0;
   5:         private readonly object _counterLock = new object();
   6:         
   7:         public MultiThreadTester(int threadCount)
   8:         {
   9:             this._threadCount = threadCount;
  10:         }
  11:  
  12:         public override void Start()
  13:         {
  14:             _aStopWatch.Start();
  15:  
  16:             for (int i = 0; i < _threadCount; i++)
  17:             {
  18:                 Thread aThread = new Thread(() => WorkInThread());
  19:                 aThread.Start();
  20:             }
  21:         }
  22:  
  23:         public override long GetElapsedMillisecondsOfIncreaseCounter()
  24:         {
  25:             return this._aStopWatch.ElapsedMilliseconds;
  26:         }
  27:  
  28:         public override bool IsTesterRunning()
  29:         {
  30:             return _aStopWatch.IsRunning;
  31:         }
  32:  
  33:         private void WorkInThread()
  34:         {
  35:             while (true)
  36:             {
  37:                 lock (_counterLock)
  38:                 {
  39:                     if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  40:                     {
  41:                         _aStopWatch.Stop();
  42:                         break;
  43:                     }
  44:  
  45:                     this.IncreaseCounter();
  46:                 }
  47:             }
  48:         }
  49:     }

Program的Main函数中,根据用户的选择来决定执行哪个测试类。

   1: class Program
   2:     {
   3:         static void Main(string[] args)
   4:         {
   5:  
   6:             string inputText = GetUserChoice();
   7:  
   8:             while (!"4".Equals(inputText))
   9:             {
  10:                 ThreadTester tester = GreateThreadTesterByInputText(inputText);
  11:                 tester.Start();
  12:  
  13:                 while (true)
  14:                 {
  15:                     Console.WriteLine(GetStatusOfThreadTester(tester));
  16:                     if (!tester.IsTesterRunning())
  17:                     {
  18:                         break;
  19:                     }
  20:                     Thread.Sleep(100);
  21:                 }
  22:  
  23:                 inputText = GetUserChoice();
  24:             }
  25:  
  26:             Console.Write("Click enter to exit...");
  27:         }
  28:  
  29:         private static string GetStatusOfThreadTester(ThreadTester tester)
  30:         {
  31:             return string.Format("[耗时{0}ms] counter = {1}, {2}",
  32:                     tester.GetElapsedMillisecondsOfIncreaseCounter(), tester.GetCounter(),
  33:                     tester.IsTesterRunning() ? "running" : "stopped");
  34:         }
  35:  
  36:         private static ThreadTester GreateThreadTesterByInputText(string inputText)
  37:         {
  38:             switch (inputText)
  39:             {
  40:                 case "1":
  41:                     return new SingleThreadTester();
  42:                 case "2":
  43:                     return new TwoThreadSwitchTester();
  44:                 default:
  45:                     return new MultiThreadTester(100);
  46:             }
  47:         }
  48:  
  49:         private static string GetUserChoice()
  50:         {
  51:             Console.WriteLine(@"==Please select the option in the following list:==
  52: 1. SingleThreadTester
  53: 2. TwoThreadSwitchTester
  54: 3. MultiThreadTester
  55: 4. Exit");
  56:  
  57:             string inputText = Console.ReadLine();
  58:  
  59:             return inputText;
  60:         }
  61:     }

三个测试类,运行结果如下:

Single Thread:
[耗时407ms] counter = 100000001, stopped
[耗时453ms] counter = 100000001, stopped
[耗时412ms] counter = 100000001, stopped

Two Thread Switch:
[耗时161503ms] counter = 100000001, stopped
[耗时164508ms] counter = 100000001, stopped
[耗时164201ms] counter = 100000001, stopped

Multi Threads - 100 Threads:
[耗时3659ms] counter = 100000001, stopped
[耗时3950ms] counter = 100000001, stopped
[耗时3720ms] counter = 100000001, stopped

Multi Threads - 2 Threads:
[耗时3078ms] counter = 100000001, stopped
[耗时3160ms] counter = 100000001, stopped
[耗时3106ms] counter = 100000001, stopped

什么是线程上下文切换

上下文切换的精确定义可以参考: http://www.linfo.org/context_switch.html。多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,为了让用户感觉这些任务正在同时进行,操作系统的设计者巧妙地利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能,但同时也带来了保存现场和加载现场的直接消耗。(Note. 更精确地说, 上下文切换会带来直接和间接两种因素影响程序性能的消耗. 直接消耗包括: CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉; 间接消耗指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小).

context-switch

根据上面上下文切换的定义,我们做出下面的假设:

  1. 之所以TwoThreadSwitchTester执行速度最慢,因为线程上下文切换的次数最多,时间主要消耗在上下文切换了,两个线程交替计数,每计数一次就要做一次线程切换。
  2. “Multi Threads - 100 Threads”比“Multi Threads - 2 Threads”开的线程数量要多,导致线程切换次数也比后者多,执行时间也比后者长。

由于Windows下没有像Linux下的vmstat这样的工具,这里我们使用Process Explorer看看程序执行的时候线程上线文切换的次数。

Single Thread:

image

计数期间,线程总共切换了580-548=32次。(548是启动程序后,初始的数值)

Two Thread Switch:

image

计数期间,线程总共切换了33673295-124=33673171次。(124是启动程序后,初始的数值)

Multi Threads - 100 Threads:

image

计数期间,线程总共切换了846-329=517次。(329是启动程序后,初始的数值)

Multi Threads - 2 Threads:

image

计数期间,线程总共切换了295-201=94次。(201是启动程序后,初始的数值)

从上面收集的数据来看,和我们的判断基本相符。

干活的其实是CPU,而不是线程

再想想原来学过的知识,之前一直以为线程多干活就快,简直是把学过的计算机原理都还给老师了。真正干活的不是线程,而是CPU。线程越多,干活不一定越快。

那么高并发的情况下什么时候适合单线程,什么时候适合多线程呢?

适合单线程的场景:单个线程的工作逻辑简单,而且速度非常快,比如从内存中读取某个值,或者从Hash表根据key获得某个value。Redis和Node.js这类程序都是单线程,适合单个线程简单快速的场景。

适合多线程的场景:单个线程的工作逻辑复杂,等待时间较长或者需要消耗大量系统运算资源,比如需要从多个远程服务获得数据并计算,或者图像处理。

例子程序:http://pan.baidu.com/s/1c05WrGO

参考:

  • Context Switch – Wikipedia
  • 多线程的代价
  • Threading in C#
  • 为什么我要用 Node.js? 案例逐一介绍
  • 知乎——redis是个单线程的程序,为什么会这么快呢?每秒10000?这个有点不解,具体是快在哪里呢?EPOLL?内存?多线程
<think>好的,用户想用CPU来运行CycleGAN模型进行训练或推理。首先,我需要回忆一下CycleGAN的基本实现框架,比如常见的PyTorch或TensorFlow版本,以及如何在代码中设置设备参数。根据引用[4]提到的训练步骤,可能涉及优化器、生成器和判别器的损失计算等部分。 接下来,用户的问题集中在CPU运行上。通常深度学习框架如PyTorch允许用户指定设备,比如使用CPU而不是GPU。我需要检查代码中是否有硬编码为GPU的地方,比如是否有`cuda()`或`to('cuda')`这样的调用,需要将其改为`cpu`。 然后,考虑到用户可能使用的是MindSpore框架,因为引用[3]提到基于MindSpore实现CycleGAN。这时需要查阅MindSpore的文档,确认在CPU上运行是否需要修改特定的配置。例如,MindSpore中使用`context.set_context(device_target='CPU')`来指定设备。 另外,数据加载部分也可能影响设备的使用,需要确保数据预处理和输入管道不会默认使用GPU加速。可能还需要调整批量大小,因为CPU的内存限制通常比GPU大,但计算速度,所以适当调整可以提高效率。 还要注意,使用CPU训练会比GPU很多,尤其是CycleGAN这样的复杂模型。用户可能需要了解训练时间的预期,并建议他们考虑简化模型或减少数据量作为备选方案。 最后,确保提供的代码示例正确无误,并且用户能够清楚每一步修改的目的。可能需要分步骤说明如何修改设备设置、检查依赖库是否支持CPU,以及运行时的注意事项。</think>要在CPU上训练或推理CycleGAN模型,需根据使用的框架调整设备配置。以下是具体步骤: ### 一、框架判断与代码调整 1. **PyTorch实现**(常见方案) 在代码中修改所有`.cuda()`和`.to('cuda')`为`.cpu()`或`device=torch.device('cpu')`: ```python # 修改前 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 修改后 device = torch.device('cpu') ``` 2. **MindSpore实现**(如引用[3]) 在初始化代码中添加设备设置: ```python import mindspore.context as context context.set_context(mode=context.GRAPH_MODE, device_target='CPU') ``` ### 二、依赖库检查 - 确保安装CPU版本的深度学习框架(如PyTorch的CPU版本) - 验证numpy等科学计算库已正确安装 ### 三、运行注意事项 1. **训练时** - 批处理大小可适当增大(CPU内存通常比GPU显存大) - 预期训练速度会下降约10-20倍(与CPU性能相关) 2. **推理时** - 单张图像处理时间约增加5-10秒 - 示例推理代码: ```python # 加载预训练模型时指定map_location generator = torch.load('cyclegan_generator.pth', map_location='cpu') ``` ### 四、性能优化建议 - 启用多核并行计算: ```python torch.set_num_threads(8) # PyTorch设置CPU线程数 ``` - 使用内存映射文件处理大型数据集
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值