为什么多线程处理此代码会导致时序不一致?

如何解决为什么多线程处理此代码会导致时序不一致?

| 我具有处理大图像的功能。根据规格,该图像最大可以为55mb。该处理需要将图像分为几个不同的波段,然后通过将这些波段重新添加到输出图像中来重构图像。由于图像太大,因此无法在32位系统上同时将所有四个图像以及输入和输出图像保留在内存中。结果,我将每个映像放在磁盘上,然后分批读取。 在多线程之前,伪代码如下所示:
for (y is 0 to ysize)
   unsigned short* ptr1 = ReadLineFromDisk(image1,y)
   unsigned short* ptr2 = ReadLineFromDisk(image2,y)
   unsigned short* ptr3 = ReadLineFromDisk(image3,y)
   unsigned short* ptr4 = ReadLineFromDisk(image4,y)
   unsigned short* outPtr = &(outImage[y*inXSize])
   for (x is 0 to xsize,++x,++ptr1,++ptr2,++ptr3,++ptr4,++outPtr){
        outPtr = combination of ptr1,ptr2,ptr3,ptr4;
   }
}
该代码在具有高性能计数器的标准500 gb硬盘驱动器的双核计算机上运行3秒钟。 如果将从磁盘读取的行数增加到大约100,然后逐步执行,代码如下所示:
chunksize = 100;
for (y is 0 to ysize by chunksize)
   unsigned short* ptr1 = ReadChunkFromDisk(image1,y)
   unsigned short* ptr2 = ReadChunkFromDisk(image2,y)
   unsigned short* ptr3 = ReadChunkFromDisk(image3,y)
   unsigned short* ptr4 = ReadChunkFromDisk(image4,y)
   unsigned short* outPtr = &(outImage[y*inXSize])
   for (x is 0 to xsize*chunk,ptr4;
   }
}
该代码比以前的代码要快1.5秒。 问题1:为什么代码更快? 我假设它的速度更快,因为以我的经验,对于相同数量的数据,大而连续的读取要快于较小的读取。也就是说,如果我一次读取全部100行数据,则至少比常规(非SSD)硬盘驱动器要快100次单独读取。我的假设接近正确吗? 即使这样,这里也不会大量使用处理器。增加缓存大小实际上是令人望而却步的,因为1.5是我所能获得的最好的值,然后该值似乎下降了一点(不确定为什么会这样,除非可能有一些磁盘缓存在起作用)。那导致我 问题2:为什么块大小会出现一个最佳点? 如果我了解这里的内容(但实际上我不认为我知道),那么如果所有内容都可以存储在内存中,那将是非常快的,因为不会有磁盘命中。如果阅读更多也使事情变得更快,难道一次读不到四分之一的图像只会对速度造成轻微影响吗? 因此,然后我切换到将外部循环放在lambda表达式中,并使用Intel的TBB来执行代码,例如:
chunksize = 100;
parallel_for (y is 0 to ysize by chunksize in a lambda expression)
   unsigned short* ptr1 = ReadChunkFromDisk(image1,ptr4;
   }
}
此代码的速度范围从0.4秒到1.6秒。 这使我想到: 问题3:速度增加最多不应该是2倍,而不是4倍吗? 这是我运行这些基准测试的双核计算机,因此,在理想情况下,一个线程从磁盘读取数据,而另一个线程在处理数据。即使以4倍的速度运行时,它也仅使用80%的处理器,而不是100%,因此仍然存在磁盘瓶颈。但是增长4倍意味着其他事情也在发生。 我还假设速度差异很大,这是因为线程在读取时不能完全同步,如果这就是速度增加的方式。真正的最后一个问题是: 问题4:如何才能使速度提高4倍?     

解决方法

答案1:是的,您是磁盘受限的,所以不会过多地占用CPU,是的,读取较大的块会更有效(只要块与磁盘缓存对齐)。 答案2:具有8 MB高速缓存并以10k RPM旋转的磁盘可能会获得60至80 MB /秒的吞吐量,因此“最佳位置”将是读取与高速缓存大小对齐的块。您可以增加缓冲区,但要使其与缓存大小保持一致:即8MB,16MB,32MB等。 答案3:理想情况下,您希望将一个线程专用于从磁盘读取数据,而另一个线程则用于处理数据(您可能希望使用多个线程进行处理)。读取磁盘的多线程可能会稍微提高性能,但是通常不是这样。我不知道为什么当您将价格提高4倍时会想到“其他情况”。 答案3更新: 坦白地说,我也不完全知道为什么会发生这种情况,但是我也已经在.NET应用程序上的多线程磁盘I / O中看到了这一点。事实上,我什至有一个C#测试示例,它演示了您注意到的相同类型的性能提高。请注意,在测试中,我正在加载HTML页面,这些页面大致与您在“ wild \”中看到的内容相同(每个页面大约80至160 KB),因此我没有将读取的内容与磁盘缓存对齐。实际上,一次读取多个线程可能更有效率,因为尽管您正在执行多次读取,但您仍在利用磁盘缓存。当然,这只是一个临时的假设,即我尚无证据可支持,因此请带一点盐!我认为,如果文件足够大,并且磁盘读取线程实际上具有与磁盘缓存对齐的缓冲区,那么添加更多线程根本不会提高速度。如果您仍然看到速度有所提高,请告诉我们! 答案4: 请尝试以下操作: 将缓冲区与磁盘的缓存大小对齐。 减少同时尝试访问磁盘的应用程序数量。 尽可能在内存中加载尽可能多的映像,并运行足够的线程以充分利用CPU(您会临时增加线程数,四处浏览,看看“最佳位置”)。 仅使用一个磁盘读取线程,并确保其不断读取!!! 再说一次,您受磁盘限制,因此您可能永远无法真正获得100%的CPU使用率。 答案4更新: 我不认为英特尔的TBB确实是导致您(和我)看到的性能提高的原因……正如我所说,我的最佳猜测是多个线程实际上可能更高效如果它们可以更好地利用磁盘缓存。我什至不确定这是否是正确的假设,所以不要在没有测试的情况下引用我! 阅读: 我找到了一篇非常详细的论文,标题为“多磁盘商品系统上的异步/多线程I / O”,这是一项性能研究,它对多线程I / O优于单线程I / O的情况进行了一些惊人的分析和测试。看看第86页。 Dobbs博士也有一篇关于该主题的文章,尽管我没有机会阅读整篇文章,但我只是浏览了一下。     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-