【问题标题】:Operator= slowing down simulation操作员=减慢模拟
【发布时间】:2022-01-14 10:09:32
【问题描述】:

我正在对聚合物进行蒙特卡罗模拟。系统当前状态的整个配置由名为Grid 的对象给出。这是我对Grid的定义:

class Grid{

public:
    std::vector <Polymer> PolymersInGrid;                    // all the polymers in the grid 
    int x;                                                   // length of x-edge of grid 
    int y;                                                   // length of y-edge of grid 
    int z;                                                   // length of z-edge of grid 
    double kT;                                               // energy factor 
    double Emm_n ;                                           // monomer-solvent when Not aligned 
    double Emm_a ;                                           // monomer-solvent when Aligned
    double Ems;                                              // monomer-solvent interaction
    double Energy;                                           // energy of grid 
    std::map <std::vector <int>, Particle> OccupancyMap;     // a map that gives the particle given the location
    

    Grid(int xlen, int ylen, int zlen, double kT_, double Emm_a_, double Emm_n_, double Ems_): x (xlen), y (ylen), z (zlen), kT (kT_), Emm_n(Emm_n_), Emm_a (Emm_a_), Ems (Ems_) {        // Constructor of class
        // this->instantiateOccupancyMap(); 
    };


    // Destructor of class 
    ~Grid(){                                    

    }; 

    // assignment operator that allows for a correct transfer of properties. Important to functioning of program. 
    Grid& operator=(Grid other){
        std::swap(PolymersInGrid, other.PolymersInGrid); 
        std::swap(Energy, other.Energy); 
        std::swap(OccupancyMap, other.OccupancyMap);
        return *this; 
    } 
.
.
.
}

如果需要,我可以了解对象PolymerParticle 的详细信息。

在我的驱动程序代码中,这就是我要执行的操作: 定义最大迭代次数。

  1. 定义一个完整的网格G
  2. 创建名为 G_G 的副本。
  3. 我正在扰乱G_的配置。
  4. 如果 G_ 上的扰动按照 Metropolis 标准被接受,我将 G_ 分配给 G (G=G_)。
  5. 重复步骤 1-4,直到达到最大迭代次数。

这是我的驱动代码:

auto start = std::chrono::high_resolution_clock::now(); 
Grid G_ (G); 
    int acceptance_count = 0; 
    for (int i{1}; i< (Nmov+1); i++){

        // choose a move 
        G_ = MoveChooser(G, v);  

        if ( MetropolisAcceptance (G.Energy, G_.Energy, G.kT) ) {
            // accepted
            // replace old config with new config

            acceptance_count++; 
            std::cout << "Number of acceptances is " << acceptance_count << std::endl;
            G = G_;
        }


        else {
            // continue;
        }

        if (i % dfreq == 0){
            G.dumpPositionsOfPolymers (i, dfile) ;
            G.dumpEnergyOfGrid(i, efile, call) ; 
        }
        // G.PolymersInGrid.at(0).printChainCoords();


    }
    
    
    
    auto stop = std::chrono::high_resolution_clock::now(); 
    
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds> (stop-start); 

    std::cout << "\n\nTime taken for simulation: " << duration.count() << " milliseconds" << std::endl;

这是有趣的部分:如果我使用没有很多“接受度”(低温、不良溶剂)的条件运行模拟,则模拟运行得非常快。但是,如果有大量接受,模拟会变得非常慢。 我的假设是我的赋值运算符 = 正在减慢我的模拟速度。 我进行了一些测试:

接受次数 = 25365,挂钟时间 = 717770 毫秒 (!)

接受次数 = 2165,挂钟时间 = 64412 毫秒

接受次数 = 3000,挂钟时间 = 75550 毫秒

而且这种趋势还在继续。 谁能建议我如何提高效率?我认为,由于 = 运算符,有没有办法绕过我正在经历的减速?

非常感谢您对我的任何建议!

【问题讨论】:

  • 你在复制值,试试G = std::move(G_)
  • Grid&amp; operator=(Grid other) 调用构造函数时似乎确实效率低下,Grid&amp; operator=(const Grid&amp;) = default; Grid&amp; operator=(Grid&amp;&amp;) = default; 似乎更好(不知道为什么不复制所有成员,可能更好地使用命名函数)。
  • @Jarod42,谢谢你的建议。你能举个例子来说明你的意思吗?

标签: c++ oop optimization operator-overloading


【解决方案1】:

你当然可以做的一件事来提高性能是强制移动_G,而不是把它转移到G

 G = std::move(G_);

毕竟在这个阶段你已经不需要G_了。

旁白。您不需要复制operator= 中的所有成员数据这一事实表明您的Grid 设计远非完美,但是,如果程序很小并且您确定您可以控制一切,请保留它。无论如何,而不是使用operator=,您应该定义并使用具有有意义名称的成员函数,例如“fast_and_dirty_swap”等:-) 然后您可以按照@Jarod42 建议的方式定义operator=,即使用= default.


我在 C++11 之前使用的另一种方法是对指针进行操作。在这种情况下,一个将有两个Grids,一个是“真实的”,一个被视为缓冲区或沙箱,并且在接受时将简单地交换指针,因此填充有MoveChooser 的“缓冲区”将成为真实的,当前的Grid

伪代码:

  • 创建两个缓冲区,previouscurrent,每个缓冲区都能够存储模拟状态
  • 初始化current
  • 创建两个指针,p_prev = &amp;previousp_curr = &amp;currenrt
  • 根据需要执行多个步骤
    • *p_curr 计算下一个状态并将其存储在*p_prev 中(例如monte_carlo_step(p_curr, p_prev)
    • 交换指针:现在当前系统状态为p_curr,之前的系统状态为p_prev
  • 分析存储在*p_curr的结果

【讨论】:

  • 感谢您的回复!这是有趣的东西。我摆脱了我的 operator= 和析构函数,并使用了默认的复制构造函数。现在事情变得更快了。但是,指针交换是什么意思?
  • 我在 G 上执行移动后创建了 G_。在我看来,G_ 是我用 MoveChooser 填充的缓冲区。在接受时,如何交换 G_ 和 G 的指针?我现在有这两个物体漂浮在周围,G 和 G_。您是否建议按照 std::(&G, &G_) 的方式做一些事情?
  • 不,不。我只是说在 C++11 之前没有 && 并且人们使用指针代替。你有两个缓冲区和两个指向它们的指针。缓冲区将包含先前和当前状态。在更新其中一个缓冲区的状态后,将交换指针,以便其中一个始终指向当前状态,另一个始终指向前一个状态。但这只是表示另一种方法的评论。
  • 我为基于指针的替代方法添加了伪代码。
  • 如果您仍然需要,我还可以帮助您避免走路,但我需要更多详细信息:stackoverflow.com/questions/69913536/…
猜你喜欢
  • 1970-01-01
  • 2013-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多