MIT6.828_HW6_Threads and Locking

该博客围绕MIT6.828_HW6线程与锁作业展开。作业要求在多核真实计算机上用哈希表探索并行编程。分析了多线程运行时出现丢失keys的原因,加入互斥虽解决丢失问题但完成时间增加,去除不必要的锁后,完成时间降低且无丢失情况,还给出最终代码思路。

MIT6.828_HW6_Threads and Locking

在此作业中,您将使用哈希表探索使用线程和锁的并行编程。 你应该在具有多个核心的真实计算机(不是xv6,而不是qemu)上完成这项功课。

开的虚拟机只有单CPU单核,所以两个线程与一个线程的程序跑出来是两倍的效果。需要先关闭虚拟机,修改处理器数1,核心数2。再次运行。

./a.out 2
0: put time = 0.007024
1: put time = 0.008762
0: get time = 6.424977
0: 2093 keys missing
1: get time = 6.535123
1: 2093 keys missing
completion time = 6.550038

./a.out 1
0: put time = 0.013344
0: get time = 6.519561
0: 0 keys missing
completion time = 6.533387

两点:1)1个线程完成时间与2个线程的完成时间大致相同,但2个线程操作数量是其两倍; 我们正在实现良好的并行性。 2)2个线程的输出表示 miss 了很多 keys 。 在你的运行中,可能会 miss 更多或更少的keys。如果您使用1个线程运行,则永远不会miss任何keys。

putget中加入互斥。

static 
void put(int key, int value)
{
  
  int i = key % NBUCKET;
  pthread_mutex_lock(&lock);
  insert(key, value, &table[i], table[i]);
  pthread_mutex_unlock(&lock);
  
}
static struct entry*
get(int key)
{
  struct entry *e = 0;
  pthread_mutex_lock(&lock);
  for (e = table[key % NBUCKET]; e != 0; e = e->next) {
    if (e->key == key) break;
  }
  pthread_mutex_unlock(&lock);
  return e;
}

首先阅读一下源码,弄明白为什么会产生missing。

  • keys[]由随机数产生。
  • 对应的线程在put时会将hash table中key值赋值为数组key[]某一段(以2个线程为例,就是0线程赋值为keys[]的前一半),value值置为其线程序号(0,1…等等)。
  • 然后将各个key与value加入对应的桶中(有点像hash的链式解决冲突)。

而在多线程的情况下,当一个线程1进入insert()还未进行插入时,轮转到另一个线程2进行了插入。回到线程1,此时的n还是为空,再进行连接时,就会丢失掉线程2插入的表项。

putget中加入互斥后,再次执行./a.out 2, 确实不会有miss keys的问题了,但是完成时间却上升到15S之久。可见在操作临界区时,多线程带来的效果并不一定会更好,有时反而会更差。

./a.out 2
0: put time = 0.003763
1: put time = 0.014141
0: get time = 15.346588
0: 0 keys missing
1: get time = 15.346687
1: 0 keys missing
completion time = 15.365085

  1. 修改代码,以便在保持正确性的同时并行运行。 (提示:在这个应用程序中,“get”中为确保正确的锁是否必要?)
    基于我们以上的分析,没有必要加入锁,因为其并不修改table。在去除get中的锁后再次执行两个线程程序。完成时间降低到6.0s,并且没有missing。
 ./a.out 2
0: put time = 0.015451
1: put time = 0.016926
0: get time = 5.991161
0: 0 keys missing
1: get time = 5.998054
1: 0 keys missing
completion time = 6.015187

  1. 修改代码,使一些put操作并行运行,同时保持正确性。
    最终put与get代码如下。
static 
void put(int key, int value)
{
  
  int i = key % NBUCKET;
  pthread_mutex_lock(&lock);
  insert(key, value, &table[i], table[i]);
  pthread_mutex_unlock(&lock);
  
}

static struct entry*
get(int key)
{
  struct entry *e = 0;
  for (e = table[key % NBUCKET]; e != 0; e = e->next) {
    if (e->key == key) break;
  }
  return e;
}
<think>我们被要求解释`torch.set_num_threads(1)`和`torch.set_num_interop_threads(1)`的作用。根据引用[1]和PyTorch官方文档,这两个函数分别控制PyTorch在CPU上并行计算的不同方面。 ### 1. `torch.set_num_threads(int)` 的作用 - 此函数设置用于**intra-op并行**的线程数,即单个操作内部(如矩阵乘法、卷积等)使用的线程数[^1]。 - 例如,当执行一个大型矩阵乘法时,PyTorch会使用该设置数量的线程来并行计算。 - 默认情况下,PyTorch会使用所有可用的CPU核心。 - 设置为1表示禁用操作内部的并行,强制使用单线程执行每个操作。 ### 2. `torch.set_num_interop_threads(int)` 的作用 - 此函数设置用于**inter-op并行**的线程数,即多个独立操作之间(如多个并行的JIT任务)的线程协调数[^1]。 - 例如,在异步执行多个独立操作时(如在JIT解释器中),这些操作可以分配到不同的线程上运行。 - 同样,设置为1表示这些操作将串行执行,而非并行。 - 注意:该设置必须在任何并行操作开始前调用,且只能调用一次,否则会报错(如用户之前遇到的错误)[^1]。 ### 关键区别 | 函数 | 控制范围 | 应用场景 | |------|----------|----------| | `torch.set_num_threads()` | 单个操作内部的并行(intra-op) | 加速单个复杂操作(如大矩阵运算) | | `torch.set_num_interop_threads()` | 多个操作之间的并行(inter-op) | 协调多个独立操作的并发执行 | ### 使用场景示例 - **单线程环境**:在资源受限环境(如嵌入式设备)或避免CPU竞争时,可同时设置两者为1: ```python torch.set_num_threads(1) # 禁用操作内并行 torch.set_num_interop_threads(1) # 禁用操作间并行 ``` - **性能调优**:在CPU上运行模型时,根据任务类型调整: - 计算密集型操作(如矩阵乘法):增加`num_threads`以利用多核。 - 多个小操作并行:增加`num_interop_threads`以提升吞吐量。 ### 注意事项 1. **调用时机**: - `torch.set_num_interop_threads()`必须在任何计算开始前调用(包括导入其他可能触发并行的库)[^1]。 - 建议在导入PyTorch后立即设置。 2. **环境变量替代**: - 可通过环境变量全局设置(优先级低于代码设置): ```bash export OMP_NUM_THREADS=1 # 控制OpenMP线程(影响intra-op) export MKL_NUM_THREADS=1 # 控制MKL线程 export TVM_NUM_THREADS=1 # 控制TVM线程 ``` 3. **错误处理**: - 若在计算后调用`torch.set_num_interop_threads()`,会触发`RuntimeError`(如用户之前的错误)。 ### 总结 - **`torch.set_num_threads(1)`**:强制每个操作使用单线程,避免多核竞争,适用于简单任务或调试。 - **`torch.set_num_interop_threads(1)`**:强制多个操作串行执行,防止任务调度开销,适用于小操作或资源紧张场景。 §§相关问题§§ 1. 如何避免PyTorch在CPU上使用所有核心? 2. PyTorch中inter-op并行和intra-op并行的实际应用有哪些? 3. 在Docker容器中运行PyTorch时如何限制CPU使用? </think> ### `torch.set_num_threads(1)` 与 `torch.set_num_interop_threads(1)` 的作用详解 #### 1. **`torch.set_num_threads(1)` - 控制操作内并行度 (intra-op)** - **作用**:限制单个操作(如矩阵乘法、卷积等)内部使用的线程数为 1[^1]。 - **影响**: - 禁用操作内部的多线程并行化 - 强制 PyTorch 使用单线程执行每个计算操作 - 适用于简单计算或避免多核竞争的场景 - **示例场景**: ```python import torch torch.set_num_threads(1) # 设置所有操作内部使用单线程 # 后续矩阵乘法将不会使用多核加速 a = torch.randn(1000, 1000) b = torch.mm(a, a) # 单线程执行 ``` #### 2. **`torch.set_num_interop_threads(1)` - 控制操作间并行度 (inter-op)** - **作用**:限制并行执行多个独立操作时的协调线程数为 1。 - **影响**: - 禁用操作间的异步并行调度 - 强制操作按顺序串行执行 - 适用于避免线程调度开销或资源竞争的场景 - **关键限制**: - 必须在**任何计算开始前调用**(包括导入其他库) - 只能调用一次,后续调用会引发 `RuntimeError` - **正确用法**: ```python import torch torch.set_num_interop_threads(1) # 必须在最开头调用! from nnunet.inference import predict # 后续操作将串行执行 ``` #### 3. **两者对比** | 特性 | `set_num_threads(1)` | `set_num_interop_threads(1)` | |------|----------------------|-------------------------------| | **控制对象** | 操作内部并行 (intra-op) | 操作间并行 (inter-op) | | **典型场景** | 禁用单任务多核加速 | 禁用多任务并行调度 | | **调用时机** | 可随时调用 | 必须在所有计算前调用 | | **线程类型** | 计算线程 | 协调线程 | | **性能影响** | 降低单操作速度 | 增加任务间延迟 | #### 4. **使用建议** 1. **资源受限环境**:在嵌入式设备或容器中同时设置两者为 1: ```python import torch torch.set_num_threads(1) torch.set_num_interop_threads(1) # 必须在前 ``` 2. **调试用途**:定位多线程相关 bug 时强制单线程执行 3. **性能优化**:结合环境变量使用效果更佳: ```bash export OMP_NUM_THREADS=1 # 控制 OpenMP 线程 export MKL_NUM_THREADS=1 # 控制 MKL 数学库线程 ``` > **注意**:在 `nnUNet` 等框架中,若需修改线程数,应在入口脚本最顶部设置,避免触发 `RuntimeError: cannot set number of interop threads...`[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值