【问题标题】:Fastest way to create a vector of indices from distance matrix in C++在 C++ 中从距离矩阵创建索引向量的最快方法
【发布时间】:2019-10-10 23:04:32
【问题描述】:

我有一个距离矩阵D,大小为n,由n 和一个常量L 作为输入。我需要创建一个向量v 包含D 中的所有条目,使其值最多为L。这里v 必须按特定顺序v = [v1 v2 .. vn] 其中vi 包含D 的第i 行中的条目,其值最多为L。每个vi 中的条目顺序并不重要。

我想知道有没有一种使用向量、数组或任何数据结构+并行化的快速方法来创建v。我所做的是使用 for 循环,对于大型 n 来说非常慢。

vector<int> v;
for (int i=0; i < n; ++i){
    for (int j=0; j < n; ++j){
        if (D(i,j) <= L) v.push_back(j);
    }
}

【问题讨论】:

  • 要考虑改进您的解决方案,我们首先需要一些关于如何计算D 内容的基本信息。就目前而言,您只将每个元素与L 比较一次,我认为这是绝对最小值。为了改进这一点,您需要能够知道某些特定值自动意味着可以从测试中排除某些范围(动态编程)。
  • 要考虑的一件事是D 的内存布局和/或您的特定访问模式可能对缓存不友好。遍历大型数据集时的缓存未命中可能会成为巨大的瓶颈。
  • 'D' 是使用 eigen 库存储的 'int' 类型的矩阵。 'D' 从文件中读取,速度很快。我有一个巨大的内存,我可以存储一个矩阵 D 最多 n = 200.000
  • 好的,如果您不想提供实际信息,这取决于您。请记住,对于处理器中的 L1/L2 缓存而言,拥有“巨大内存”并没有任何帮助,当然也不能作为忽略程序内存布局和访问模式的借口,尤其是在您寻求有关性能问题的建议时.

标签: c++ performance for-loop parallel-processing


【解决方案1】:

最好的方法主要取决于上下文。如果您正在寻求 GPU 并行化,您应该看看 OpenCL。

对于基于 CPU 的并行化,C++ 标准 #include &lt;thread&gt; 库可能是您最好的选择,但您需要小心:

  • 创建线程需要时间,因此如果 n 相对较小(
  • D(i,j) 必须同时被多个线程读取
  • v 必须是多线程可写的,标准向量不会削减它

v 可能是以 vi 作为其子向量的 2d 向量,但这些必须在并行化之前初始化:

std::vector<std::vector<int>> v; 
v.reserve(n);                    
for(size_t i = 0; i < n; i++)
{
    v.push_back(std::vector<int>());
}

您需要决定要使用多少线程。如果这仅适用于一台机器,则硬编码是一个有效的选项。线程库中有一个函数可以获取支持的线程数量,但它更多的是提示而不是可信。

size_t threadAmount = std::thread::hardware_concurrency(); //How many threads should run hardware_concurrency() gives you a hint, but its not optimal
std::vector<std::thread> t;                                //to store the threads in
t.reserve(threadAmount-1);                                 //you need threadAmount-1 extra threads (we already have the main-thread)

要启动一个线程,你需要一个它可以执行的函数。在这种情况下,这是读取矩阵的一部分。

void CheckPart(size_t start, size_t amount, int L, std::vector<std::vector<int>>& vec)
{
    for(size_t i = start; i < amount+start; i++)
    {
        for(size_t j = 0; j < n; j++)
        {
            if(D(i,j) <= L)
            {
                vec[i].push_back(j);
            }
        }
    }
}

现在您需要将矩阵拆分为大约 n/threadAmount 行的部分并启动线程。线程构造函数需要一个函数及其参数,但它总是会尝试复制参数,即使函数需要引用。为了防止这种情况,您需要强制使用 std::ref() 的引用

int i = 0;
int rows;
for(size_t a = 0; a < threadAmount-1; a++)
{
    rows = n/threadAmount + ((n%threadAmount>a)?1:0);
    t.push_back(std::thread(CheckPart, i, rows, L, std::ref(v)));
    i += rows;
}

线程现在正在运行,所有要做的就是运行主函数的最后一个块:

SortPart(i, n/threadAmount, L, v);

之后你需要等待线程完成并清理它们:

for(unsigned int a = 0; a < threadAmount-1; a++)
{
    if(t[a].joinable())
    {
        t[a].join();
    }
}

请注意,这只是一个快速而肮脏的例子。不同的问题可能需要不同的实现,由于我无法猜测上下文,所以我能提供的帮助非常有限。

【讨论】:

    【解决方案2】:

    考虑到 cmets,我做了适当的更正(强调)。

    您是否搜索过编写性能代码、线程、asm 指令(如果您的程序集不是您想要的)和 OpenCL 进行并行处理的技巧?如果没有,我强烈推荐!

    某些情况下,将所有 for 循环变量声明在 for 循环之外(以避免多次声明它们)会使其更快,但在这种情况下并非如此(来自我们的朋友帕迪)

    此外,使用 newvector 可以更快,正如我们在这里看到的:Using arrays or std::vectors in C++, what's the performance gap? - 我测试过,使用 vector 比使用 new 慢 6 秒,这只需 1 秒。我想当有人在搜索性能时,不需要 std::vector 带来的安全性和易于管理性保证,即使使用 new 并不是那么困难,只要避免计算的堆溢出并记住使用 delete[]

    user4581301 在这里是正确的,下面的说法是不正确的: 最后,如果你将D 构建在一个数组而不是矩阵中(或者如果你将D 复制到一个常量数组中,也许......),它将更加缓存友好,并将节省一个 for 循环语句。

    【讨论】:

    • vector 完成的初始化不会使程序减慢很多,而且您正在权衡std::vector 带来的安全性和易于管理的保证。不要轻易这样做。确保确实有必要进行分析,并在完成后再次进行分析以确保它确实有所作为。
    • 旁注:D(i,j) 表示法表明可能已经学习了将一维数组用于矩阵的经验。
    • 这句话是完全不正确的:“将所有的 for 循环变量声明在 for 循环之外...会使其更快”。好吧,让我来限定一下。在某些情况下它是正确的,但在此问题中提供的示例的情况下不是