2017-04-12 72 views
1

Disclamer:我使用的是英特尔编译器2017,如果您想知道为什么要这样做,请在问题结尾处进行。通过引用传递太多参数可能效率低下?

我有这样的代码:

class A{ 
    vector<float> v; 
    ... 
    void foo(); 
    void bar(); 
} 

void A::foo(){ 
    for(int i=0; i<bigNumber;i++){ 
    //something very expensive 
    //call bar() many times per cycle; 
    } 
} 

void A::bar(){ 
    //... 
    v.push_back(/*something*/); 
} 

现在,假设我想并行foo(),因为它是非常昂贵的。但是,由于v.push_back(),我不能简单地使用#pragma omp parallel for

据我所知,这里还有两种选择:

  1. 我们使用#pragma omp critical
  2. 我们创造的v为每个线程的本地版本,然后我们共同他们在平行节结束,更或更少,如解释here

解决方案1.通常被认为是一个糟糕的解决方案,因为竞争条件会产生一致的开销。

然而,解决2.需要修改bar()这样:

class A{ 
    vector<float> v; 
    ... 
    void foo(); 
    void bar(std::vector<float> &local_v); 
} 

void A::foo(){ 
    #pragma omp parallel 
    { 
    std::vector<float> local_v; 
    #pragma omp for 
    for(int i=0; i<bigNumber;i++){ 
     //something very expensive 
     //call bar(local_v) many times per cycle; 
    } 
    #pragma omp critical 
    { 
     v.insert(v.end(), local_v.begin(), local_v.end()); 
    } 
    } 
} 

void A::bar(std::vector<float> &local_v){ 
    //... 
    v.push_back(/*something*/); 
} 

到目前为止好。现在,假设不仅存在v,而且存在10个向量,比如v1v2,...,v10,或者无论如何10个共享变量。另外,我们假设bar不是直接在foo()之内调用,而是在多次嵌套调用之后调用。类似于foo(),它调用foo1(std::vector<float> v1, ..., std::vector<float> v10),它调用foo2(std::vector<float> v1, ..., std::vector<float> v10),重复此嵌套调用很多次,直到最后一次调用bar(std::vector<float> v1, ..., std::vector<float> v10)。因此,这看起来像一个可维护性的噩梦(我必须修改所有嵌套函数的所有标题和调用)......但更重要的是:我们同意通过引用是有效的,但它总是一个指针复制。正如你所看到的,这里很多指针被复制了很多次。所有这些副本是否可能导致效率低下?其实我最关心的就是性能,所以如果你告诉我“没关系,这很好,因为编译器是超级智能的,他们做了一些魔术,所以你可以复制一万亿次引用,并且性能没有下降”,那么它会很好,但我不知道这样的魔法是否存在。

为什么我这样做: 我试图并行this代码。特别是,我将whilehere改写为for,它可以并行化,但如果您按照代码进行操作,则会发现调用的onAffineShapeFound被调用,这将修改共享对象keys的状态。这发生在其他许多变量中,但这是此代码的“最深”情况。

+0

不知道你是否可以用openmp来完成,但你也可以初始化矢量到完整大小,然后给每个线程一段矢量工作。是的,他们都将在同一个向量上工作,但由于它们都不会触及相同的元素,并且没有任何线程修改向量的状态,所以它应该是线程安全的。 – NathanOliver

+0

@NathanOliver感谢您的回答。这是不可能的,因为没有办法事先知道'键'的大小。 – justHelloWorld

+0

“*正如你所看到的,这里有很多指针被复制了很多次,有可能所有这些副本都会导致效率低下吗?*” - 与之相比,究竟是什么?如果'T'是一个小的类型,比如'char'或者甚至是'int',唯一一次'void foo(T&t)'比'void foo(T t)'效率低。在任何其他情况下,使用引用的性能几乎总是更好。 – Xirema

回答

1

a::Bar()a::Bar(std::vector<float> & v)之间的直接比较中,区别在于第二个版本必须将堆栈的大小增加8个字节,而不是原始版本的大小。在性能方面,这是一个非常小的效果:无论函数是否包含参数,都必须调整堆栈指针(所以唯一真正的区别是单个指针副本,甚至可以根据编译器对其进行优化),并且就功能本身的实际性能而言,不断向std::vector添加元素将是一项非常昂贵的操作,尤其是如果矢量需要重新分配(这可能会频繁发生,这取决于有多大该向量需要得到),这意味着这些成本将远远超过指针复制的成本。

所以,简短版本:坚持与参考。

+0

关键是,正如我在我的问题中解释的那样,这个技巧不仅在一种情况下完成,而且在几个对象中完成,所以它不仅是'v',而是更多关于'v1,v2,...,vn '。正如我在第一条评论中所说的那样,我们不知道'v'的大小,但我可以'保留'一个合理的数量(谢谢,你给了我这个想法)。我认为唯一真正的解决方案就是实施两个解决方案,就像有人建议并分析他们两个 – justHelloWorld

+0

如果我将所有这些参数“包装”到一个结构中并仅将其作为参考传递,该怎么办?每个调用只有一个副本(指向结构对象的指针),对吗? – justHelloWorld

+0

@justHelloWorld是的,但这几乎肯定会被视为不成熟的优化。了解原始调用堆栈的代价是多么昂贵,您的新调用堆栈的代价是多么昂贵,并且了解您是否实际上使用这些抽象来节省大量CPU周期。我会把95%的几率放在某个地方,这些优化实际上并不重要。 – Xirema