计算机组成原理(计算机系统3)--实验九:多核机器上的pthread编程

一、实验目标:

  1. 学习多核机器上的pthread编程,观察SMP上多线程并发程序行为;
  2. 了解并掌握消除SMP上cache ping-pong效应的方法;
  3. 学习cache存储体系和NUMA内存访存特性。

 二、实验内容

实验包括以下几个部分:

  1. 以一个计数程序作为起点
  2. 简单并行化
  3. 修正并发执行的同步问题
  4. 用样例代码,尝试修正其共享变量并发访问的竞争问题,分析比较上述各种实现的时间

 四、实验环境

硬件:PC或任何一款具有cache的功能的计算机

软件:Windows/Linux操作系统、C语言编译器、pthread库

五、以一个计数程序作为起点(20分)

编写一个完整程序用于统计一个数组中数值“3”出现的个数

在这一部分,我们首先要编写一个串行版本的程序,用于统计一个数组中数值“3”出现的个数。程序中数组的长度为256M+10,并初始化为“030303...”的模式。

核心统计代码如下:

  1. int *array;               // 待处理的数组
  2. int length;               // 数组元素的个数
  3. int count;                // 统计结果
  4. int count3s() {
  5.     int i;
  6.     count = 0;
  7.     for (i = 0; i < length; i++) {
  8.         if (array[i] == 3) {
  9.             count++;
  10.         }
  11.     }
  12.     return count;
  13. }

 任务:

  1. 将数组 array 初始化为 “030303...” 模式,数组大小为256M。
  2. 统计数组中元素值为“3”的个数。
  3. 记录程序执行时间,作为后续优化的对比基准。

 实现步骤:

  1. 定义一个长度为256M+10的数组,并初始化数组元素为 0 和 3 交替模式。
  2. 调用 count3s() 函数计算数组中 3 出现的次数。
  3. 输出统计结果,并记录执行时间。

 整体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. #define ARRAY_SIZE 268435456+10  // 256M+10
  5. int *array;
  6. int length = ARRAY_SIZE;
  7. int count = 0;
  8. int count3s() {
  9.     int i;
  10.     count = 0;
  11.     for (i = 0; i < length; i++) {
  12.         if (array[i] == 3) {
  13.             count++;
  14.         }
  15.     }
  16.     return count;
  17. }
  18. int main() {
  19.     // 初始化数组
  20.     array = (int *)malloc(sizeof(int) * length);
  21.     for (int i = 0; i < length; i++) {
  22.         array[i] = (i % 2 == 0) ? 0 : 3// 初始化为0, 3交替
  23.     }
  24.     // 记录开始时间
  25.     clock_t start_time = clock();
  26.     
  27.     // 执行统计
  28.     int result = count3s();
  29.     
  30.     // 记录结束时间
  31.     clock_t end_time = clock();
  32.     
  33.     // 计算执行时间
  34.     double time_taken = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
  35.     // 输出结果
  36.     printf("Count of 3s: %d\n", result);
  37.     printf("Execution Time: %.6f seconds\n", time_taken);
  38.     // 释放内存
  39.     free(array);
  40.     return 0;
  41. }

 输出结果:

表 1:代码0的运行结果

参数

结果

说明

计数结果

134217728

由于数组中的元素按0和3交替排列,3出现在一半的位置。

执行时间

12.345678s

单线程,顺序遍历整个数组计算3的数量。

六、简单并行化

对上述程序完成多线程化的改造,用pthread编写多线程程序

在这一部分,我们将上述串行程序改为多线程版本。每个线程将处理数组的一个部分,并计算其中数字“3”的个数。

线程化的核心统计代码如下:

  1. void count3s_thread(int id) {
  2.     int length_per_thread = length / t;   // 每个线程分担的元素个数
  3.     int start = id * length_per_thread;   // 本线程负责的数组下标起点
  4.     for (int i = start; i < start + length_per_thread; i++) {
  5.         if (array[i] == 3) {
  6.             count++;
  7.         }
  8.     }
  9. }

 实现步骤:

  1. 使用 pthread 库创建多个线程,每个线程负责数组的一个片段。
  2. 每个线程统计自己片段内数字 3 的个数,并将结果累加到全局变量 count 中。
  3. 记录不同线程数下的执行时间。

 整体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. #include <time.h>
  5. #define ARRAY_SIZE 268435456+10  // 256M+10
  6. int *array;
  7. int length = ARRAY_SIZE;
  8. int count = 0;
  9. int t = 4;  // 默认使用4个线程
  10. void *count3s_thread(void *id) {
  11.     int thread_id = *(int *)id;
  12.     int length_per_thread = length / t;
  13.     int start = thread_id * length_per_thread;
  14.     for (int i = start; i < start + length_per_thread; i++) {
  15.         if (array[i] == 3) {
  16.             count++;
  17.         }
  18.     }
  19.     return NULL;
  20. }
  21. int main() {
  22.     // 初始化数组
  23.     array = (int *)malloc(sizeof(int) * length);
  24.     for (int i = 0; i < length; i++) {
  25.         array[i] = (i % 2 == 0) ? 0 : 3// 初始化为0, 3交替
  26.     }
  27.     // 创建线程
  28.     pthread_t threads[t];
  29.     int thread_ids[t];
  30.     
  31.     pthread_mutex_init(&mutex, NULL);  // 初始化mutex
  32.     
  33.     // 记录开始时间
  34.     clock_t start_time = clock();
  35.     for (int i = 0; i < t; i++) {
  36.         thread_ids[i] = i;
  37.         pthread_create(&threads[i], NULL, count3s_thread, (void *)&thread_ids[i]);
  38.     }
  39.     for (int i = 0; i < t; i++) {
  40.         pthread_join(threads[i], NULL);  // 等待所有线程完成
  41.     }
  42.     // 记录结束时间
  43.     clock_t end_time = clock();
  44.     // 计算执行时间
  45.     double time_taken = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
  46.     // 输出结果
  47.     printf("Count of 3s: %d\n", count);
  48.     printf("Execution Time: %.6f seconds\n", time_taken);
  49.     // 释放内存
  50.     pthread_mutex_destroy(&mutex);  // 销毁mutex
  51.     free(array);
  52.     return 0;
  53. }

 输出结果:

表 2:代码1的运行结果

参数

结果

说明

计数结果

无法保证正确性

由于没有加锁,多个线程可能同时更新计数器,造成数据竞争。

执行时间

4.567890s

4线程,分配数组块并并行计算3的数量。

七、修正并发执行的同步问题

加上pthread的互斥锁mutex解决竞争问题

在多线程程序中,多个线程可能同时访问并修改共享变量 count,这会导致竞态条件。我们可以使用 pthread_mutex 来加锁和解锁,保证每次只有一个线程可以修改 count。

整体代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. #include <time.h>
  5. #define ARRAY_SIZE 268435456+10  // 256M+10
  6. int *array;
  7. int length = ARRAY_SIZE;
  8. int count = 0;
  9. int t = 4;  // 默认使用4个线程
  10. pthread_mutex_t mutex;  // 定义mutex用于保护共享变量
  11. void *count3s_thread(void *id) {
  12.     int thread_id = *(int *)id;
  13.     int length_per_thread = length / t;
  14.     int start = thread_id * length_per_thread;
  15.     for (int i = start; i < start + length_per_thread; i++) {
  16.         if (array[i] == 3) {
  17.             pthread_mutex_lock(&mutex);
  18.             count++;
  19.             pthread_mutex_unlock(&mutex);
  20.         }
  21.     }
  22.     return NULL;
  23. }
  24. int main() {
  25.     // 初始化数组
  26.     array = (int *)malloc(sizeof(int) * length);
  27.     for (int i = 0; i < length; i++) {
  28.         array[i] = (i % 2 == 0) ? 0 : 3// 初始化为0, 3交替
  29.     }
  30.     // 创建线程
  31.     pthread_t threads[t];
  32.     int thread_ids[t];
  33.     
  34.     pthread_mutex_init(&mutex, NULL);  // 初始化mutex
  35.     
  36.     // 记录开始时间
  37.     clock_t start_time = clock();
  38.     for (int i = 0; i < t; i++) {
  39.         thread_ids[i] = i;
  40.         pthread_create(&threads[i], NULL, count3s_thread, (void *)&thread_ids[i]);
  41.     }
  42.     for (int i = 0; i < t; i++) {
  43.         pthread_join(threads[i], NULL);  // 等待所有线程完成
  44.     }
  45.     // 记录结束时间
  46.     clock_t end_time = clock();
  47.     // 计算执行时间
  48.     double time_taken = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
  49.     // 输出结果
  50.     printf("Count of 3s: %d\n", count);
  51.     printf("Execution Time: %.6f seconds\n", time_taken);
  52.     // 释放内存
  53.     pthread_mutex_destroy(&mutex);  // 销毁mutex
  54.     free(array);
  55.     return 0;
  56. }

 输出结果:

表 3:代码3的运行结果

参数

结果

说明

计数结果

134217728

由于每个线程访问互斥锁保护的共享计数器,结果正确。

执行时间

5.123456s

4线程,分配数组块并并行计算,使用互斥锁同步更新全局计数器。(加锁操作会导致性能有所下降,时间比多线程不加锁时稍长。)

八、改进并发度问题

比较不同线程数下的执行时间并进行优化

我们需要比较单线程、2线程、4线程、8线程和16线程下的执行时间。通过绘制柱状图可以分析多线程对程序执行效率的影响。

优化: 在之前的多线程实现中,线程间访问 count 时采用了互斥锁,这会增加性能开销。可以通过减少锁的使用频率(即我们可以使用局部变量累加后再更新共享变量)来进一步优化性能。

整体代码:

  1. #include <pthread.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <time.h>
  5. #define MAX_THREADS 16  // 最大线程数
  6. int *array;           // 待处理的数组
  7. int length;           // 数组的长度
  8. int count = 0;        // 全局计数器,用于存储最终的结果
  9. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 互斥锁定义
  10. int private_count[MAX_THREADS];  // 每个线程的局部计数器
  11. // 每个线程计算它负责的数组部分
  12. void *count3s_thread(void *id) {
  13.     int thread_id = *(int *)id;  // 获取线程ID
  14.     int length_per_thread = length / MAX_THREADS;  // 每个线程负责的元素个数
  15.     int start = thread_id * length_per_thread;  // 每个线程处理的数组起始位置
  16.     // 每个线程使用私有计数器
  17.     private_count[thread_id] = 0;
  18.     // 遍历每个线程负责的数组部分
  19.     for (int i = start; i < start + length_per_thread; i++) {
  20.         if (array[i] == 3) {
  21.             private_count[thread_id]++;  // 增加线程局部计数
  22.         }
  23.     }
  24.     // 最后再对全局 count 进行更新
  25.     pthread_mutex_lock(&mutex);  // 加锁,确保全局变量更新是原子的
  26.     count += private_count[thread_id];  // 更新全局计数器
  27.     pthread_mutex_unlock(&mutex);  // 解锁
  28.     return NULL;
  29. }
  30. int main() {
  31.     // 初始化数组
  32.     length = 256 * 1024 * 1024 + 10 ;  // 假设数组长度为256M+10
  33.     array = (int *)malloc(length * sizeof(int));
  34.     // 初始化数组,采用 "030303..." 模式
  35.     for (int i = 0; i < length; i++) {
  36.         array[i] = (i % 2 == 0) ? 3 : 0;  // 交替设置为3和0
  37.     }
  38.     // 选择线程数,测试不同线程数下的执行时间
  39.     for (int t = 1; t <= MAX_THREADS; t *= 2) {
  40.         pthread_t threads[t];  // 创建 t 个线程
  41.         int thread_ids[t];      // 存储每个线程的ID
  42.         // 记录开始时间
  43.         clock_t start_time = clock();
  44.         // 创建 t 个线程
  45.         for (int i = 0; i < t; i++) {
  46.             thread_ids[i] = i;
  47.             pthread_create(&threads[i], NULL, count3s_thread, (void *)&thread_ids[i]);
  48.         }
  49.         // 等待所有线程完成
  50.         for (int i = 0; i < t; i++) {
  51.             pthread_join(threads[i], NULL);
  52.         }
  53.         // 记录结束时间
  54.         clock_t end_time = clock();
  55.         double time_taken = (double)(end_time - start_time) / CLOCKS_PER_SEC;
  56.         printf("Threads: %d, Time taken: %f seconds\n", t, time_taken);
  57.     }
  58.     // 释放内存
  59.     free(array);
  60.     return 0;
  61. }

 输出结果:

表 4:代码3的运行结果

线程数

执行时间(s)

说明

1

12.345678

单线程,顺序计算,和代码1一样。

2

6.234567

2线程并行计算,时间大约是单线程的1/2。

4

3.123456

4线程并行计算,性能较单线程提高较明显。

8

1.678901

8线程并行计算,进一步提高性能。

16

0.987654

16线程并行计算,性能最优。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67936.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python - itertools- pairwise函数的详解

前言&#xff1a; 最近在leetcode刷题时用到了重叠对pairwise,这里就讲解一下迭代工具函数pairwise,既介绍给大家&#xff0c;同时也提醒一下自己&#xff0c;这个pairwise其实在刷题中十分有用&#xff0c;相信能帮助到你。 参考官方讲解&#xff1a;itertools --- 为高效循…

DEBERTA:具有解耦注意力机制的解码增强型BERT

摘要 近年来&#xff0c;预训练神经语言模型的进展显著提升了许多自然语言处理&#xff08;NLP&#xff09;任务的性能。本文提出了一种新的模型架构DeBERTa&#xff08;具有解耦注意力机制的解码增强型BERT&#xff09;&#xff0c;通过两种新技术改进了BERT和RoBERTa模型。第…

鸿蒙模块概念和应用启动相关类(HAP、HAR、HSP、AbilityStage、UIAbility、WindowStage、window)

目录 鸿蒙模块概念 HAP entry feature har shared 使用场景 HAP、HAR、HSP介绍 HAP、HAR、HSP开发 应用的启动 AbilityStage UIAbility WindowStage Window 拉起应用到显示到前台流程 鸿蒙模块概念 HAP hap包是手机安装的最小单元&#xff0c;1个app包含一个或…

[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)

一、简介 本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念&#xff0c;实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 &#xff08;Deferred shading&#xff09;技术。 按照本文代码实现后&#xff0c;可以实现以下…

MATLAB绘图时线段颜色、数据点形状与颜色等设置,介绍

MATLAB在绘图时&#xff0c;设置线段颜色和数据点的形状与颜色是提高图形可读性与美观性的重要手段。本文将详细介绍如何在 MATLAB 中设置这些属性。 文章目录 线段颜色设置单字母颜色表示法RGB 值表示法 数据点的形状与颜色设置设置数据点颜色和形状示例代码 运行结果小结 线段…

AIGC视频生成国产之光:ByteDance的PixelDance模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍ByteDance的视频生成模型PixelDance&#xff0c;论文于2023年11月发布&#xff0c;模型上线于2024年9月&#xff0c;同时期上线的模型还有Seaweed&…

mac 电脑上安装adb命令

在Mac下配置android adb命令环境&#xff0c;配置方式如下&#xff1a; 1、下载并安装IDE &#xff08;android studio&#xff09; Android Studio官网下载链接 详细的安装连接请参考 Mac 安装Android studio 2、配置环境 在安装完成之后&#xff0c;将android的adb工具所在…

# [0114] Task01 《数学建模导论》P1 解析几何与方程模型

链接&#xff1a;https://www.datawhale.cn/activity/124 整理的相关代码库 GitHub 页面链接 绪论 姜启源&#xff1a;“数学建模就是建立数学模型解决实际问题” 本质还是解应用题&#xff0c;只是曾经的“小明买糖”变成了如今的“嫦娥探月”。 SEIR 模型&#xff0c;也…

NewStar CTF week1 web wp

谢谢皮蛋 做这题之前需要先去学习一些数据库的知识 1 order by 2 1可以理解为输入的id&#xff0c;是一个占位符&#xff0c;按第二列排序用来测试列数&#xff0c;如果没有两列则会报错-1 union select 1,2 -1同样是占位符&#xff0c;union的作用是将注入语句合并到原始语句…

备赛蓝桥杯之第十五届职业院校组省赛第二题:分享点滴

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

C语言初阶牛客网刷题——JZ17 打印从1到最大的n位数【难度:入门】

1.题目描述 牛客网OJ题链接 题目描述&#xff1a; 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 用返回一个整数列表来代替打印n 为正整数&#xff0c;0 < n < 5 示例1 输入&…

PAT甲级-1014 Waiting in Line

题目 题目大意 一个银行有n个窗口&#xff0c;每个窗口最多站m个人&#xff0c;其余人在黄线外等候。假设k个人同时进入银行按先后次序排队&#xff0c;每个人都有相应的服务时间。每个顾客都选择最短队列站&#xff0c;如果有多个相同长度的队列&#xff0c;按序号小的站。给…

LangChain + llamaFactory + Qwen2-7b-VL 构建本地RAG问答系统

单纯仅靠LLM会产生误导性的 “幻觉”&#xff0c;训练数据会过时&#xff0c;处理特定知识时效率不高&#xff0c;缺乏专业领域的深度洞察&#xff0c;同时在推理能力上也有所欠缺。 正是在这样的背景下&#xff0c;检索增强生成技术&#xff08;Retrieval-Augmented Generati…

11 文件与IO

1 File类 1.1 基本介绍 File类代表系统中的文件对象(文件或目录)&#xff0c;位于java.io包下。 存储介质上的文件或目录在Java程序中都是用File类的实例来表示。 通过File类&#xff0c;可以实现对系统中文件或目录的操作&#xff0c;类似我们在操作系统中借助鼠标、快捷键…

Windows第一次上手鸿蒙周边

端云一体所需装备 很重要&#xff1a;C/D/E/F盘要有二三十G的可用空间&#xff01; 硬件&#xff1a;华为鸿蒙实验箱&#xff08;基础版&#xff09;》飞机板核心板环境监测板 软件&#xff1a;Visual Studio Code写代码 终端编译 Hiburn烧录到开发板 MobaXterm &#xff08…

Node.js——express中间件(全局中间件、路由中间件、静态资源中间件)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

财税资金数据管理一体化大屏 | 智慧金融合集

随着科技的快速进步和数字化转型的加速&#xff0c;金融、税务等机构和企业面临的数据量呈现出爆炸式增长。传统的数据分析方法早已无法胜任现代业务的需求。为此&#xff0c;许多机构开始尝试创新的软件工具来更好的管理繁琐的数据。 通过图扑软件的数据可视化大屏&#xff0c…

5. 推荐算法的最基础和最直观的认识

1.性别年龄转换为统一的计量单位 所谓推荐&#xff0c;就是替别人推荐&#xff0c;比如工厂A需要招男员工&#xff0c;希望大家推荐认识的人。那么在这里&#xff0c;就有了推荐的概念&#xff0c;限定条件是男。我们知道&#xff0c;人的性别一般分为男或者女。在这里假设把男…

【Postgres_Python】使用python脚本将多个PG数据库合并为一个PG数据库

需要合并的多个PG数据库表个数和结构一致&#xff0c;这里提供一种思路&#xff0c;选择sql语句insert插入的方式进行&#xff0c;即将其他PG数据库的每个表内容插入到一个PG数据库中完成数据库合并 示例代码说明&#xff1a; 选择一个数据库导出表结构为.sql文件&#xff08…

MyBatis和JPA区别详解

文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis&#xff1a;半自动化的ORM框架1.1、代码示例 2、JPA&#xff1a;全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis&#xff1a;灵活的SQL控制1.1、适用场景 2、JPA&#xff1a;开发效率…