[共享]多线程模型效率分析

dfasri 2011-11-01 04:06:36
加精
无聊散分贴

欢迎大家也拿身边的接触到的多线程模型来分析其性能, 共享自己多线程的分析过程和经验.

先说说网络的多线程无阻塞FIFO队列: MS-Queue.
当中是使用CAS(Compare and Swap)的方式来实现多线程同步, 让FIFO列表保持正确.

其实简单的分析得出, 这种CAS形式的队列, 效率是非常低下的.

在其入队和离队的时候, 都要进行循环并且调用CAS来进行尝试把指针的值进行CAS, 没错线程是没有被阻塞, 但实际上, 很明显就是CAS判断结果失败的时候, 就会继续循环, 这跟阻塞有什么区别? 然后一个时刻之内, 有且只有一个线程, 是可以顺利通过这个CAS循环体, 成功入队或离队的. 其线程都必须进行两次CAS循环以上才能够拿到数据, 相当于被阻塞等待了.

换而言之: CAS循环的判断模式, 只是一种变相的临界区, 临界区确保的是同一时间之内只有一个线程通过当前代码区, 而CAS循环判断, 也是确保同一时间之内, 只有一个线程能够顺利把头或尾指针进行赋值. 虽然这种冲突不存在于读线程和写线程之间的, 只存在于写和写, 读和读线程之间的. 但最终出来的结果, 只会有一个线程在写, 一个线程在读的最终局面. 效率是十分低下的.

最后还得要注意CPU的原操作指令XADD, XCHG, XCMPCHG, 这些指令只要在一个CPU上运行, 其他CPU是不得不停止几个周期的CPU时间的, 虽然比起临界区等等这类同步对象会节省非常多, 但是, 实际上让N个线程无阻塞的形式来死循环调用CAS直到成功, 会让CPU运行非常多的XCMPCHG指令, 直接造成整体性能下降的.

综上所述: MS-Queue就是象征性的模型, 没有任何实用价值的.
...全文
5596 205 打赏 收藏 转发到动态 举报
写回复
用AI写文章
205 条回复
切换为时间正序
请发表友善的回复…
发表回复
wumn29 2013-05-30
  • 打赏
  • 举报
回复
引用 38 楼 wyx100 的回复:
对于一些常规使用场合: 1、CRITICAL_SECTION进行共享冲突保护。 2、EVENT进行线程间通信。 这个方法在windows平台下,个人觉得是个较佳的使用方式。
这两种都是线程同步方式, 线程通信使用消息
dfasri 2011-12-01
  • 打赏
  • 举报
回复
[Quote=引用 196 楼 sundrop 的回复:]
不知道你提到的“专门的单元测试来测试单线程标准内存分配各种情况的效率”的测试结果是怎样的?
[/Quote]
我的标准内存分配单元测试里面, 是没有采用多线程的, 因为项目的实现上, 不会跨线程进行new和delete.
测试分别有:
1. 同一大小连续分配后立刻释放10万次的速度 200 ~ 300 ms 内完成
2. 同一大小连续分配10万次后, 然后连续释放10万次的速度 new 100ms左右, delete 1s以上
3. 不同大小例如1~1024每个大小都分配, 循环10次, 然后把申请都一个一个释放 new 100ms左右, delete 3 s以上.
dfasri 2011-12-01
  • 打赏
  • 举报
回复
[Quote=引用 198 楼 r3000 的回复:]

我以前工作过的项目有一个规矩感觉很好,就是从来不讨论技术问题,更不看楼主这种大段文字,
如果你对某项技术的效率感觉有问题,那好,拿出你的实际代码来,然后其他人要反驳你,好的,
也拿出代码来。
没有实际代码和数据的技术讨论,苍白无力,在我们项目里根本没人理你。但是如果你拿出的例子有
说服力,并且其他人没有更好的数据,那你的技术会立刻被采用。
[/Quote]

你的分数很高, SunDrop的分数很低.
先不管SunDrop的分析是对是错, 但起码, 人家实实在在的加入讨论, 并尝试, 测试.

你在做什么? 你连上搜索抄段MSQ的代码都懒. 说得那么好你的项目要反驳就拿出代码, 你拿出你的代码了么? 你能够把MSQ改成能够超过千万, 向千万的流量了么?

你连软件的编程基本素质都没有. 我不知道你现在年龄多少, 但可以肯定一点, 假如10年前你初学编程, 10年后的你跟10年前水平一至.
初学编程的人才会拿着急着去写代码, 看代码.
有提升以后都是先设计流程, 分析推断以后才写代码实际测试的. 只有这样写出来的程序才有质量.

说依据, 我现在的项目单元测试比实际的工程代码还多得多, 没依据我就不会说MSQ慢了.
dfasri 2011-12-01
  • 打赏
  • 举报
回复
[Quote=引用 198 楼 r3000 的回复:]
我以前工作过的项目有一个规矩感觉很好,就是从来不讨论技术问题,更不看楼主这种大段文字,
如果你对某项技术的效率感觉有问题,那好,拿出你的实际代码来,然后其他人要反驳你,好的,
也拿出代码来。
没有实际代码和数据的技术讨论,苍白无力,在我们项目里根本没人理你。但是如果你拿出的例子有
说服力,并且其他人没有更好的数据,那你的技术会立刻被采用。
[/Quote]

以这里讨论的, 也只是找些有对这些方面研究过的人, 看看是否会有更优的做法. 没代码没话说的也就代表根本没有研究过.

多线程编程, 并非一朝两日, 看看人家写的代码就可以提高效率, 没有自己的充分理解和尝试, 发表的意见都是废话.

理解并且有实际尝试, 研究过的, 有没有代码都不成问题, 流程的伪代码才是最好的讨论方式.

像你这样连个想法和抽象都没有的人一边凉快去, 分数高但见识低的人, 我最讨厌, 从来不会正面回答问题.
gameslq 2011-12-01
  • 打赏
  • 举报
回复
dfasri 2011-12-01
  • 打赏
  • 举报
回复
[Quote=引用 196 楼 sundrop 的回复:]

所以用那个程序根本测不出无锁算法的性能,只能测出 new delete 的性能

delete(new int()) 的方式不用申请任何更大的内存,不涉及缓存大数据、CPU cache 频繁切换的问题
也许可能是 new delete 最好的性能了吧...

10000 个 int 大概是 40KB
如果申请一万次,再接着释放一万次
1/ c++ runtime 可能需要多次发生系……
[/Quote]

最主要的问题的, 当我加入了内存池的管理以后, MS-Queue变得更慢...
内存池管理很简单, 就是内嵌多一个MS-Queue, 以初始化的时候就把相应数量的结点先写进去这个Queue, 但这个Queue不会new, 也不会delete, 单纯就是FIFO, 这样, 外部的MS-Queue就要写的时候就向内嵌MSQ领取结点, 读取完成的时候, 向内嵌MSQ写入结点, 就可以简单组建成内存池了. 但这样做了以后, 连new和delete的效率都不如!
康斯坦汀 2011-12-01
  • 打赏
  • 举报
回复
我以前工作过的项目有一个规矩感觉很好,就是从来不讨论技术问题,更不看楼主这种大段文字,
如果你对某项技术的效率感觉有问题,那好,拿出你的实际代码来,然后其他人要反驳你,好的,
也拿出代码来。
没有实际代码和数据的技术讨论,苍白无力,在我们项目里根本没人理你。但是如果你拿出的例子有
说服力,并且其他人没有更好的数据,那你的技术会立刻被采用。
morrist1987 2011-12-01
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 dfasri 的回复:]

引用 8 楼 fallinsky 的回复:
我最近在处理一个算法 有4万多条数据 每两条数据的不同属性根据不同权重算出一个值,然后就会要进行8亿次计算 最后对这8亿个数据排序。
我希望程序的时间能尽量缩小,有什么编程的方法可以借鉴吗????
需要用到并行?多线程?还是分布式框架(如hadoop?)

用排列与组合, 就可以得出所有运算组合.
8亿次, 其实不算太多, 毕竟现在的CP……
[/Quote]

这位大大说的在理啊。受教了。
SunDrop 2011-12-01
  • 打赏
  • 举报
回复
所以用那个程序根本测不出无锁算法的性能,只能测出 new delete 的性能

delete(new int()) 的方式不用申请任何更大的内存,不涉及缓存大数据、CPU cache 频繁切换的问题
也许可能是 new delete 最好的性能了吧...

10000 个 int 大概是 40KB
如果申请一万次,再接着释放一万次
1/ c++ runtime 可能需要多次发生系统调用,向系统申请更大的内存空间
2/ 一万个指针不断操作,会不断刷新 CPU 的缓存,导致数据在内存和 CPU 缓存之间的搬移,某种程度上也是会消耗性能的
3/ new delete 算法的具体实现,释放内存说不定要多做一些空闲空间的合并之类的操作,也会多一些计算

我的单核 CPU 这样的测试,new delete 对,Visual Studio 2008 编译 release 版程序
大概每秒只能到 90 万次左右的 new 和 delete 对,而单纯 delete(new int()) 可以达到 300 万次
这里可以看到两种测试方法的差别

多个线程交错申请释放内存更加考验具体 new delete 实现的优劣和并发的能力吧

不知道你提到的“专门的单元测试来测试单线程标准内存分配各种情况的效率”的测试结果是怎样的?
dfasri 2011-12-01
  • 打赏
  • 举报
回复
[Quote=引用 194 楼 sundrop 的回复:]
期望入队出队跑到 2千万,至少这一点上我觉得不大可能
凡事都是要拿出数据来才能令人信服
[/Quote]
假如你限制队列只能够用new和delete的话, 根本不可能上千万, 有几百万已经很不错了. 但采用内存池的方式, 不上千万就是你代码的错了.

我说的测试代码里面, 我的意思是指连续new1万次, 保留1万个指针, 然后这1万个指针再一起delete的意思, 直接delete(new)的方式, 不是真正的new和delete的效率, 实际应用不可能new一个立马接着就delete一个的.
SunDrop 2011-11-30
  • 打赏
  • 举报
回复
这个测试代码是有问题,后来改成下面这样



#include <windows.h>

#define __RUNTIME__ 100u /* second */

void main() {
DWORD bgn, start, i;
__int64 cnt = 0;
start = GetTickCount();
bgn = start;
do {
for (i = 0; i < 10000; ++i) {
#if 1
delete (new int()); /* run up to about 3,000,000 times per second */
#else
__asm nop; /* run up to about 1,500,000,000 times per second */
#endif
}
cnt += 10000;
if (GetTickCount() - bgn > 1000) {
cout << cnt << endl;
cnt = 0;
bgn = GetTickCount();
}
} while (GetTickCount() - start < __RUNTIME__ * 1000);
return;
}



new delete 跑到了 300 万,nop 操作跑到了 15 亿(当然循环每次加一也占用一些时间,不是单纯的10000次nop指令)

楼主敢于质疑的态度是好的,不能盲目相信老外写的东西

程序的好坏是算法决定的,但同时也是实现所决定的
一个很差的实现不能用来评价算法的好坏
new delete 单 CPU 只能跑到 300 万次/秒,4 核最多也就 1200 万次吧
期望入队出队跑到 2千万,至少这一点上我觉得不大可能

凡事都是要拿出数据来才能令人信服
dfasri 2011-11-30
  • 打赏
  • 举报
回复
不能够立刻删除, 也不能够立即重用

打错字了
dfasri 2011-11-30
  • 打赏
  • 举报
回复
[Quote=引用 191 楼 sundrop 的回复:]
i520M 算是 3GHz 主频,4核,就是 12GHz
[/Quote]
有M后缀的是手提用的, 只2.4G主频, 双核, 超线程变4核而已. CPU的实际效率, 不能够直接用叠加的方式来算的.

[Quote=引用 191 楼 sundrop 的回复:]
除了 new delete 其他部分会不会有瓶颈。。。
[/Quote]
CAS也是一个瓶颈, 因为同一时间, 只有一个线程可以通过, 哪怕采用"帮助完成"的方式, 但只能够说是同一时间, 可以有两个线程是成功的. 而且CAS这类原操作调用的时候, CPU访问内存的总线会被锁定, 所以访问内存的操作均会等待, 但只是几个CPU周期. 假如一个线程不停的调用CAS, 那么其他所有线程的内存读写处理速度, 也会下降不少的. 这点已经证实了.

[Quote=引用 191 楼 sundrop 的回复:]
delete (new int());
[/Quote]
这一段代码并不能够真实反应Windows的new和delete的速度, 较准确的方式, 是new 1万次, 然后再delete1万次, 你会发现new的时候需要几百ms, delete的时候需要上秒. 不过我不清楚多线程的情况下, 一个线程new, 另一个线程delete会有什么效率. 我有专门的单元测试来测试单线程标准内存分配各种情况的效率的.

nop是指当前CPU跳过一个周期不做事. 4千万次, 只代码当前的CPU1s有4000万个周期不做事, 但2.4G来说, 会有更多时间来运行其他代码. 除了nop, 其他的代码就占用了2.4G - 4千万的CPU时间了.

我是想知道MS-Queue的效率, 有没有人写过测试过其效率. 我抄回来的MSQ, 只单单跑250万. 而且MSQ不用new和delete, 其实很麻烦, 在当前结点被读取后, 不能够立刻删除, 也不能够立即使用的, 因为有可能其他线程还在尝试访问这个结点, 所以sinservice说的方案里面包含有"危险指针"这个延迟delete处理.
SunDrop 2011-11-30
  • 打赏
  • 举报
回复
楼主可以用下面代码测试一下单线程 new delete 的性能
我的机器 Intel Celeron(R) CPU 2.4GHz
new delete 一秒钟也只跑了 250 万次左右
nop 操作也只跑到 4000 多万次


#include <windows.h>

#define __RUNTIME__ 100u /* second */

void main() {
DWORD bgn, start;
DWORD cnt = 0;
start = GetTickCount();
bgn = start;
do {
#if 1
delete (new int()); /* run up to about 2,500,000 times per second */
#else
__asm nop; /* run up to about 45,000,000 times per second */
#endif
cnt += 1;
if (GetTickCount() - bgn > 1000) {
cout << cnt << endl;
cnt = 0;
bgn = GetTickCount();
}
} while (GetTickCount() - start < __RUNTIME__ * 1000);
return;
}
SunDrop 2011-11-30
  • 打赏
  • 举报
回复
这个。。每秒钟 2千万次 是什么概念?

i520M 算是 3GHz 主频,4核,就是 12GHz
假如 cpu 每个时钟周期处理一条指令,就是 120 亿条指令
120亿条指令 / 2千万次操作 = 600 条指令/次操作

你测的 200万次的话,就是 6000 条指令/次操作

恩。。这个计算合理不?

恩。。一个 new delete 要多少条指令?
可以跑四个线程,什么不干专门进行 new delete 看能跑到多少次

还有 new delete 内部需要操作同一个数据区么?
有没有临界区,有没有瓶颈

除了 new delete 其他部分会不会有瓶颈。。。
dfasri 2011-11-26
  • 打赏
  • 举报
回复
sinservice
今天, 把网上的MS-Queue代码抄了下来, 测试一下并发读写的速度. 开了三个线程写入, 主线程读取.
虽然没有用内存池, 但其结果只有200万左右. 机器是i520M的CPU, 双核超线程.
要用内存池的话, 就得要在MS-Queue里面内嵌多一个MS-Queue的形式, 然后写入的时候向这个内嵌的申请内存, 读取出来以后就向这个MS-Queue丢回去.

不过即使用的是new和delete, 也不至于慢成这样子吧? 想知道一下你写的效率是如何的? 有没有相应的单元测试程序让我拿来测试一下速度? 只需要三个线程写入, 一个线程读取, 都是写入INT64的即可
dfasri 2011-11-26
  • 打赏
  • 举报
回复
http://people.csail.mit.edu/edya/publications/OptimisticFIFOQueue-journal.pdf
这个文档, 详细介绍了相关的性能, 但没有硬件和环境的描述的..虽然Dequeue和Enqueue应该是同等时间的操作来的, 但4核的时候, 假如真的是4个线程, 那么不是慢得可以了? 不过那是几年前的CPU, 跟现在的没得比...
dfasri 2011-11-26
  • 打赏
  • 举报
回复
[Quote=引用 187 楼 sinservice 的回复:]
200万,多长时间? 用什么语言实现的? debug还是release版本。而且,请你最好参照msq的原论文,里面有性能测试的报告,以那个为标准比较好。
[/Quote]

每秒算一次, 用if (GetTickCount() - dwTick > 1000)的方式进行统计的. 这个抄回来的MS-Queue不知道是改进版还是原版, 原本是C#写的, 带有"帮助"完成插入的方式的. 有一篇详细介绍改进版的MSQ的, 里面只标识了线程数以核数算, 然后每秒钟支持多少个的形式, 不过里面既没有说多少个写多少个读, 也没有写什么硬件. 里面通常只标识1000 ~ 1300 / ms 的次数, 假如是这个次数的话, 那岂不是效率很低, 1秒连2千万次读或写都不到..
「已注销」 2011-11-26
  • 打赏
  • 举报
回复
[Quote=引用 186 楼 dfasri 的回复:]

sinservice
今天, 把网上的MS-Queue代码抄了下来, 测试一下并发读写的速度. 开了三个线程写入, 主线程读取.
虽然没有用内存池, 但其结果只有200万左右. 机器是i520M的CPU, 双核超线程.
要用内存池的话, 就得要在MS-Queue里面内嵌多一个MS-Queue的形式, 然后写入的时候向这个内嵌的申请内存, 读取出来以后就向这个MS-Queue丢回去.

……
[/Quote]

200万,多长时间? 用什么语言实现的? debug还是release版本。而且,请你最好参照msq的原论文,里面有性能测试的报告,以那个为标准比较好。

dfasri 2011-11-15
  • 打赏
  • 举报
回复
[Quote=引用 184 楼 sinservice 的回复:]
就是在并发场合里使用,比如,底层往一个队列里写,上层从这个队列里读。
[/Quote]

如果是多写多读的模型, 我想不需要这么复杂. 七猫说的方式我感觉就是最简单最快捷的了. 一点多余的判断, 重试都没有.
加载更多回复(163)

15,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧