【问题标题】:Vector access speed, which method is faster?矢量访问速度,哪种方法更快?
【发布时间】:2014-09-14 17:44:26
【问题描述】:

我很好奇在访问向量时哪种方法更快。

为了简单起见,假设我有两个对象:PlayerShip

有一个玩家指针向量vector<Player*> players,每个玩家对象包含一个飞船指针向量vector<Ship*> ships,然后每艘飞船都有几个可以调用的函数,以此类推。

在这些情况下,直接访问这些功能是否更快?还是创建一个临时对象指针来访问所有内容?

这样做是否更快:

for (int i = 0; i < players.size(); i++)
{
    for (int j = 0; j < players.at(i)->ships.size(); j++)
    {
        players.at(i)->ships.at(j)->update();
        if (
               (players.at(i)->ships.at(j)->get_x() > 0)    &&
               (players.at(i)->ships.at(j)->get_x() < screen_x)    &&
               (players.at(i)->ships.at(j)->get_y() > 0)    &&
               (players.at(i)->ships.at(j)->get_y() < screen_y)
           )
        {
            players.at(i)->visible.push_back(j);
        }
    }
}

或者创建临时指针是否更快,以便不需要不断访问向量:

for (int i = 0; i < players.size(); i++)
{
    Player* play = players.at(i);
    for (int j = 0; j < play->ships.size(); j++)
    {
        Ship* ship = play->ships.at(j);
        ship->update();

        int ship_x = ship->get_x();
        int ship_y = ship->get_y();
        if (
               (ship_x > 0)    &&
               (ship_x < screen_x)    &&
               (ship_y > 0)    &&
               (ship_y < screen_y)
           )
        {
            play->visible.push_back(j);
        }
    }
}

我知道第二个视觉上更整洁,但真的不知道它是否一定更快。

想法?

【问题讨论】:

  • 当你分析两者时发生了什么?
  • 这取决于编译器是否能够保证您的代码不会修改向量。例如,如果get_x 修改了 player 或 ship 向量会发生什么?然后需要在第一个版本中再次查找向量。当然get_x,从它的名字来看,可能不会修改向量。但是编译器无法知道,除非函数代码在那个时候是可见的(例如内联函数)
  • 更快的是operator[]而不是.at()。或者,使用现代 C++:for (Ship&amp; ship : ships) for (Player&amp; play : players)
  • 您真正应该做的一件事是将players.at(i) 替换为players[i]at() 会检查是否越界并且速度会相当慢。
  • @vsoftco 我不这么认为。这只是一个如果失败就会抛出的检查。考虑有一些语言对每个访问进行绑定检查。

标签: c++ performance pointers vector


【解决方案1】:

在我看来,对速度的强调是错误的。我认为您应该从编写更具可读性的代码开始:

auto is_visible = [=](Ship const &s) { return s.get_x() > 0 && s.get_x() < screen_x 
                                           && s.get_y() > 0 && s.get_y() < screen_y; 
                                     };

for (auto & player : players)
    std::copy_if(ships.begin(), ships.end(), 
                 std::back_inserter(player.visible),
                 is_visible);

至少在 IMO,这至少与使用 at 进行索引一样安全,但可能至少与使用 [] 一样快,并且比任何一个都更具可读性。

我可能应该再补充一点:可见性似乎并不取决于玩家。至少从代码的编写方式来看,所有玩家都将拥有相同的可见船只。如果这是正确的,您可能想要做更多类似的事情:

std::vector<Ship> visible;

std::copy_if(ships.begin(), ships.end(), 
             std::back_inserter(visible),
             [=](Ship const &s) { return s.get_x() > 0 && s.get_x() < screen_x 
                                      && s.get_y() > 0 && s.get_y() < screen_y; });

for (auto &player : players)
    player.visible = visible;

【讨论】:

  • 可读性必须是非常主观的......但是感谢您的编码想法。无论哪种方式,检查范围都不是重点。这只是一个例子来展示我所指的向量指针的使用。
  • 可能应该更清楚。每个玩家都应该拥有自己的船只矢量。所以玩家 1 不会拥有与玩家 2 相同的船只。同样,主要思想是演示所使用的方法,不一定完全完成所显示的内容。不过,感谢您提供的示例,完成某事的每一种不同方式都是值得学习的新事物。
【解决方案2】:

你应该检查一下哪个更快。

可能是第一个,也可能是第二个。如果大多数船的X坐标都是负数,那肯定是第一个。

但是,如果您觉得第二个更好(对我来说也是如此),请坚持下去。当存在实际性能问题时担心性能。

【讨论】:

  • 很公平。我很好奇访问多级指针是否会导致性能下降,或者内存偏移是否是在编译时计算的(基本上是在幕后做我在示例 2 中所做的事情。)
【解决方案3】:

我认为您在这里受到优化编译器的摆布。任何一个都可能更快,这取决于它的优化方式。

  • 在第一个版本中,编译器可能会决定 拉出players.at(i)-&gt;ships.at(j)公共子表达式, 可能用get_x()get_y() 把它变成东西 这看起来很像你的第二个版本。

  • 在第二个版本中,重新排序可能会移动 int ship_y = ship-&gt;get_y() 进入循环条件以便它可以 与ship_y &gt; 0短路。

  • 在这两种情况下,它可能决定将整个短路条件设为 成一系列快速按位和指令,消除 分店

但我的猜测是,无论哪种方式,您都不会看到太大的差异。尝试转储汇编代码进行比较,当然还有分析它。

【讨论】:

    【解决方案4】:

    感谢大家提供的信息。由于没有明确的“选项 A肯定比选项 B”快,因此我接受了您的建议并进行了基准测试。

    这是我整理的一些代码。

    基本上,它会创建 100 个玩家。每个玩家有 100 艘船的向量。每艘船都有一个由 100 名船员组成的向量。 (一旦运行,它会消耗大约 500MB 的 RAM)。

    我运行了未优化和优化的测试(-O3 标志)

    测试 1 是链指针(即 player->ship->crew->number 等) 测试 2 与测试 1 相同,但我将所有 .at() 替换为 operator[]。 测试 3 使用临时指针来访问所有内容。

    我多次运行每个测试并将结果取平均值。 这是我的结果:

    未优化:

    Test 1:  13000
    Test 2:  5500
    Test 3:  2800
    

    优化:

    Test 1:  1050
    Test 2:  650
    Test 3:  450
    

    这表明优化在所有情况下都大大提高了速度。 无论哪种方式,优化或未优化, .at() 肯定会减慢速度。使用 operator[] 明显更快。 但最终,在所有情况下,使用临时指针是最快的。

    #include <vector>
    #include <ctime>
    #include <iostream>
    
    using namespace std;
    
    class People
    {
        public:
            vector<int> number;
    };
    
    class Ship
    {
        public:
            Ship(int f);
            vector<People*> crew;
            int get_x();
            int get_y();
    
        private:
            int x;
            int y;
    };
    
    Ship::Ship(int f)
    {
        //Assign some nonsense for testing purposes
        x = f * 50;
        y = f * 75;
    }
    
    int Ship::get_x()
    {
        return x;
    }
    
    int Ship::get_y()
    {
        return y;
    }
    
    class Player
    {
        public:
        vector<Ship*> ships;
    };
    
    int main(int argc, char *argv[])
    {
        vector<Player*> players;
    
        int start, end;
        unsigned int i, j, k, l;
    
        //Create 100 players, each with 100 ships, and each ship with 100 crew.
        for (i = 0; i < 100; i++)
        {
            Player* play = new Player;
            players.push_back(play);
            for (j = 0; j < 100; j++)
            {
                Ship* new_ship = new Ship(j);
                play->ships.push_back(new_ship);
                for (k = 0; k < 100; k++)
                {
                    People* newbie = new People;
                    new_ship->crew.push_back(newbie);
                    for (l = 0; l < 100; l++)
                    {
                        newbie->number.push_back(0);
                    }
                    newbie->number.clear();
                }
            }
        }    
    
    
        //Test 1
    
    
        start = clock();
    
        for (i = 0; i < players.size(); i++)
        {
            for (j = 0; j < players.at(i)->ships.size(); j++)
            {
                for (k = 0; k < players.at(i)->ships.at(j)->crew.size(); k++)
                {
                    for (l = 0; l < 100; l++)
                    {
                        //Give each crew some number to hold on to.
                        players.at(i)->ships.at(j)->crew.at(k)->number.push_back(players.at(i)->ships.at(j)->get_x() * players.at(i)->ships.at(j)->get_y() + l);
                    }
                    //Clear the number list for the next test.
                    players.at(i)->ships.at(j)->crew.at(k)->number.clear();
                }
            }
        }
        end = clock();
    
        cout << "Test 1:   "  << (end - start) << endl;
    
    
        //Test 2
    
        start = clock();
    
        for (i = 0; i < players.size(); i++)
        {
            for (j = 0; j < players[i]->ships.size(); j++)
            {
                for (k = 0; k < players[i]->ships[j]->crew.size(); k++)
                {
                    for (l = 0; l < 100; l++)
                    {
                        players[i]->ships[j]->crew[k]->number.push_back(players[i]->ships[j]->get_x() * players[i]->ships[j]->get_y() + l);
                    }
                    players[i]->ships[j]->crew[k]->number.clear();
                }
            }
        }
        end = clock();
    
        cout << "Test 2:   "  << (end - start) << endl;
    
    
        //Test 3
    
    
        start = clock();
    
        for (i = 0; i < players.size(); i++)
        {
            Player* temp_play = players.at(i);
            for (j = 0; j < temp_play->ships.size(); j++)
            {
                Ship* temp_ship = temp_play->ships.at(j);
                for (k = 0; k < temp_ship->crew.size(); k++)
                {
                    People* temp_crew = temp_ship->crew.at(k);
                    for (l = 0; l < 100; l++)
                    {
                        temp_crew->number.push_back(temp_ship->get_x() * temp_ship->get_y() + l);
                    }
                    temp_crew->number.clear();
                }
            }
        }
        end = clock();
    
        cout << "Test 3:   "  << (end - start) << endl;
    
        return 0;
    }
    

    【讨论】:

    • 请记住,您的“测试 1”正在分配内存,而后续测试正在重用分配的内存,因此您将在第一个测试中看到内存分配开销。还有错误 - 在“测试 3”中,您的 temp_crew 使用了错误的索引(j 而不是 k) - 并且数学是“* l”而不是其他测试中的“+ l”。
    • 感谢您对小虫子的了解。至于内存分配,您的意思是它在number 上使用.push_back() 的位置吗?我想,既然我清除了它,它会再次为空,但也许不是。无论哪种方式,我都会改变它。如果您指的是我最初创建所有内容的位置,那么计时器直到完成后才会启动。无论哪种方式,都进行了一些调整并重新计算了运行时间。还是差不多。
    • clear() 不会释放内存。我尝试依次运行 3 次测试两次,第二次测试 1 的时间下降了一半以上。
    • 这是有道理的。我更新了代码以在初始设置中分配它。
    猜你喜欢
    • 1970-01-01
    • 2014-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    • 2019-11-22
    • 1970-01-01
    相关资源
    最近更新 更多