Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Multithreading 为什么这个程序的线程越多速度越慢?_Multithreading_Performance_Rust - Fatal编程技术网

Multithreading 为什么这个程序的线程越多速度越慢?

Multithreading 为什么这个程序的线程越多速度越慢?,multithreading,performance,rust,Multithreading,Performance,Rust,与仅使用一个线程运行相比,使用两个线程运行时,我的程序执行所需的时间是使用一个线程运行时的两倍 我使用以下方法创建: 有两个线程: test\u bench\u alt。。。实验台:1537465纳秒/国际热核实验堆(+/-154499) 为什么程序在使用两个线程运行时速度较慢?我们可以做些什么来加快速度 更新: 下面高度优化的C++程序执行大致相同的任务,并且(在我的机器上)扩展到19个线程,证明工作负载实际上可以并行化 #include <stdio.h> #include &

与仅使用一个线程运行相比,使用两个线程运行时,我的程序执行所需的时间是使用一个线程运行时的两倍

我使用以下方法创建:

有两个线程:

test\u bench\u alt。。。实验台:1537465纳秒/国际热核实验堆(+/-154499)
为什么程序在使用两个线程运行时速度较慢?我们可以做些什么来加快速度

更新:

下面高度优化的C++程序执行大致相同的任务,并且(在我的机器上)扩展到19个线程,证明工作负载实际上可以并行化

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <sched.h>
#include <atomic>



#define PAR 1
#define DATASIZE 524288

std::vector<std::vector<int>> output;
std::vector<int> input;


int run_job1(int task) {

    int l = DATASIZE/PAR;
    int off = task*(DATASIZE/PAR);
    auto temp = &output[task][0];
    auto ip = &input[off];
    for(int i=0;i<l;++i){
        *temp=*ip;//+off;
        temp+=1;
        ip+=1;
    }
    return 0;
}


int run_job2(int task) {
    auto& temp = output[task];
    auto temp_p = &output[task][0];
    auto temp_p2 = temp_p + DATASIZE/PAR;
    int expected = task*(DATASIZE/PAR);
    while(temp_p!=temp_p2) {
        if (*temp_p!=expected)
            printf("Woha!\n");
        temp_p+=1;
        expected+=1;
    }
    return 0;
}

std::atomic_int valsync=0;
std::atomic_int valdone=0;

void* threadfunc(void* p) {
    int i = (int)(long)p;
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(i, &set);
        sched_setaffinity(0, sizeof(set),&set);

    int expect=1;
    while(true) {
        while(valsync.load()!=expect) {
        }
        expect+=1;        
        run_job1(i);
        valdone+=1;

        while(valsync.load()!=expect) {
        }
        expect+=1;        
        run_job2(i);    
        valdone+=1;
    }

}

int main() {

    for(int i=0;i<DATASIZE;++i) {
        input.push_back(i);
    }
    for(int i=0;i<PAR;++i) {
        std::vector<int> t;
        for(int j=0;j<DATASIZE/PAR;++j)
            t.push_back(0);
        output.push_back(t);
    }
    for (int i = 0; i < PAR ; ++i)
    {
        pthread_t thread_id;
        if(pthread_create(&thread_id, NULL, threadfunc, (void*)i)) {

            fprintf(stderr, "Error creating thread\n");
            return 1;

        }   
    }
    for(int run=0;run<20;++run)
    {
        std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
        for(int j=0;j<1000;++j) {

            std::atomic_fetch_add(&valsync,1);
            while(true)  {
                int expected=PAR;
                if (std::atomic_compare_exchange_strong(&valdone,&expected,0))
                    break;

            }


            std::atomic_fetch_add(&valsync,1);
            while(true)  {
                int expected=PAR;
                if (std::atomic_compare_exchange_strong(&valdone,&expected,0))
                    break;
            }
        }
        std::chrono::steady_clock::time_point t2= std::chrono::steady_clock::now(); 
        auto delta  = t2-t1;

        std::cout<<"Time: "<<std::chrono::duration_cast<std::chrono::nanoseconds>(delta).count()/1000<<" ns per iter \n";
    }

    return 0;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
定义1标准杆
#定义数据大小524288
std::矢量输出;
向量输入;
int run_作业1(int任务){
int l=数据大小/PAR;
int off=任务*(数据大小/PAR);
自动温度=&输出[任务][0];
自动ip=&输入[关闭];

对于(int i=0;i而言,主要问题是该基准几乎毫无意义。分配和比较数字不是计算密集型操作,这意味着并行化这些操作几乎没有任何价值。正如这些测量结果所示,增加更多线程只会削弱性能

令人惊讶的是,最大的瓶颈可能是在构建输出向量时出现的其他琐碎指令,这可以通过迭代器避免。与向量的大多数交互依赖于索引运算符
[]
迭代一个集合,这是一个非常规的、未经建议的集合。下面是同一基准测试的改进版本。这些更改总结如下:

  • 可以使用
    vec
    vec![0;数据大小]
    初始化具有特定元素的向量
  • 还可以使用迭代器构建N个初始向量。使用
    Vec::new
    创建的空向量不分配堆内存,因此这基本上是可以的
  • 将作业分配给每个工作者时,输入内存块和输出向量可以压缩在一起。这两个块都会自动进行迭代,所需的边界检查要少得多。此外,由于
    ,如果给最后一个工作者一个较小的片,那么最后一个工作者不会尝试访问越界
  • 每个线程的工作也可以通过迭代器生成并收集到一个新的向量中,而不是在每一步生成一个循环,将新值推送到一个现有的可变向量中。编译器可以通过这种方法避免许多冗余检查
  • 最后,基准的第二部分不需要对“已处理”内容的可变访问
#[工作台]
pub fn试验台(b:和多台试验台){
设平行度=1;
让数据大小=500_000;
让pool=pool::new(并行化);
{
让data=vec![0;数据大小];
让mut输出_数据:Vec=(0..parallellism).map(| | Vec::new()).collect();
b、 国际热核实验堆(移动){
用于vec输入和mut输出_数据{
向量清除();
}
{
让data_ref=&data;
池。范围(|范围|{
对于(&mut输出数据)中的(输出数据块、输入数据块)
.into_iter()
.zip(数据块(数据大小/并行性))
{
范围执行(移动| |{
*output_data_bucket=将_data_chunk.into_iter().cloned().collect();
})
}
});
}
池。范围(|范围|{
用于子输入和输出_数据{
范围执行(移动| |{
对于sublot中的sublot{
断言!(*子部分,42);
}
});
}
});
});
}
}
之前:

试验台架试验台架:1352071 ns/iter(+/-516762)
之后:

试验台架试验台架:533573 ns/iter(+/-213486)
这些数字可能只会稍微好一些,多几个线程,虽然有更高的方差。对于Parallelism=2:

试验台架试验台架:314662 ns/iter(+/-340636)

如果你把一个计算密集型的算法带到方程中,那么你可以在考虑这些想法的情况下再试一次。

在对这个问题进行了广泛的研究之后,从E_net4的优秀答案中获得了很多灵感,我找到了我的原始程序缩放不好的确切原因

我们必须考虑两个不同的问题:

  • 为什么程序这么慢

  • 为什么它不能扩展到超过1个CPU

  • 问题1的答案已经由E_net4以令人印象深刻的细节和良好的精度得到了回答。问题2的答案是针对
    输出数据的/cache行抖动

    当现代多核CPU访问主内存时,它们将从内存访问的数据存储在自己的专用缓存中。对同一内存的后续请求可以从快速缓存而不是相对较慢的主内存中提供

    如果一个内核写入另一个内核缓存的内存,会发生什么情况?每当发生这种情况时,所有内核中的所有缓存副本都必须更新或删除。这是通过跟踪每个存储缓存线的状态来实现的,使用类似于的方法。对于每个缓存线,CPU都会跟踪它是否是唯一所有者。

    每个缓存通常是64字节。高速缓存行是由一个核心拥有的。现在考虑在这个程序中保存OutPuthDATA矢量的字节。每个<代码> VEC < /C> > 8×3字节(在64位机器上)=24字节。这意味着前两个输出向量可能存储在同一高速缓存行中。

    每当执行
    Vec::push
    时,
    len#include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    #include <vector>
    #include <chrono>
    #include <sched.h>
    #include <atomic>
    
    
    
    #define PAR 1
    #define DATASIZE 524288
    
    std::vector<std::vector<int>> output;
    std::vector<int> input;
    
    
    int run_job1(int task) {
    
        int l = DATASIZE/PAR;
        int off = task*(DATASIZE/PAR);
        auto temp = &output[task][0];
        auto ip = &input[off];
        for(int i=0;i<l;++i){
            *temp=*ip;//+off;
            temp+=1;
            ip+=1;
        }
        return 0;
    }
    
    
    int run_job2(int task) {
        auto& temp = output[task];
        auto temp_p = &output[task][0];
        auto temp_p2 = temp_p + DATASIZE/PAR;
        int expected = task*(DATASIZE/PAR);
        while(temp_p!=temp_p2) {
            if (*temp_p!=expected)
                printf("Woha!\n");
            temp_p+=1;
            expected+=1;
        }
        return 0;
    }
    
    std::atomic_int valsync=0;
    std::atomic_int valdone=0;
    
    void* threadfunc(void* p) {
        int i = (int)(long)p;
        cpu_set_t set;
        CPU_ZERO(&set);
        CPU_SET(i, &set);
            sched_setaffinity(0, sizeof(set),&set);
    
        int expect=1;
        while(true) {
            while(valsync.load()!=expect) {
            }
            expect+=1;        
            run_job1(i);
            valdone+=1;
    
            while(valsync.load()!=expect) {
            }
            expect+=1;        
            run_job2(i);    
            valdone+=1;
        }
    
    }
    
    int main() {
    
        for(int i=0;i<DATASIZE;++i) {
            input.push_back(i);
        }
        for(int i=0;i<PAR;++i) {
            std::vector<int> t;
            for(int j=0;j<DATASIZE/PAR;++j)
                t.push_back(0);
            output.push_back(t);
        }
        for (int i = 0; i < PAR ; ++i)
        {
            pthread_t thread_id;
            if(pthread_create(&thread_id, NULL, threadfunc, (void*)i)) {
    
                fprintf(stderr, "Error creating thread\n");
                return 1;
    
            }   
        }
        for(int run=0;run<20;++run)
        {
            std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
            for(int j=0;j<1000;++j) {
    
                std::atomic_fetch_add(&valsync,1);
                while(true)  {
                    int expected=PAR;
                    if (std::atomic_compare_exchange_strong(&valdone,&expected,0))
                        break;
    
                }
    
    
                std::atomic_fetch_add(&valsync,1);
                while(true)  {
                    int expected=PAR;
                    if (std::atomic_compare_exchange_strong(&valdone,&expected,0))
                        break;
                }
            }
            std::chrono::steady_clock::time_point t2= std::chrono::steady_clock::now(); 
            auto delta  = t2-t1;
    
            std::cout<<"Time: "<<std::chrono::duration_cast<std::chrono::nanoseconds>(delta).count()/1000<<" ns per iter \n";
        }
    
        return 0;
    }
    
    #[bench]
    pub fn test_bench_alt(b: &mut Bencher) {
        let parallellism = 4;
        let data_size = 500_000;
    
        let mut pool = Pool::new(parallellism);
    
        struct Filler {
            odata: Vec<i32>,
            padding: [u8; 64],
        }
    
        {
            let mut data = Vec::new();
            for _ in 0..data_size {
                data.push(0);
            }
    
            let mut output_data = Vec::<Filler>::new();
            for _ in 0..parallellism {
                let mut t = Vec::<i32>::with_capacity(data_size / parallellism);
                output_data.push(Filler {
                    odata: t,
                    padding: [0; 64],
                });
            }
            b.iter(move || {
                for i in 0..parallellism {
                    output_data[i].odata.clear();
                }
                {
                    let mut output_data_ref = &mut output_data;
                    let data_ref = &data;
                    pool.scoped(move |scope| {
                        for (idx, output_data_bucket) in output_data_ref.iter_mut().enumerate() {
                            scope.execute(move || {
                                for item in &data_ref[(idx * (data_size / parallellism))
                                                          ..((idx + 1) * (data_size / parallellism))]
                                {
                                    //Yes, this is a logic bug when parallellism does not evenely divide data_size. I could use "chunks" to avoid this, but I wanted to keep this simple for this analysis.
                                    output_data_bucket.odata.push(*item);
                                }
                            });
                        }
                    });
                }
                pool.scoped(|scope| {
                    for sub in &output_data {
                        scope.execute(move || {
                            for sublot in &sub.odata {
                                assert!(*sublot != 42);
                            }
                        });
                    }
                });
            });
        }
    }