当前位置: 首页 > 知识库问答 >
问题:

使用预取加速随机内存访问

左丘成业
2023-03-14

我试图通过使用预取来加速单个程序。我的程序的目的只是为了测试。以下是它的作用:

  1. 它使用两个大小相同的int缓冲区

第一次,第一个缓冲区中的值包含其索引的值(参见下面代码中的函数createIndexBuffer)。

在我的程序代码中会更清楚:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/time.h>

#define BUFFER_SIZE ((unsigned long) 4096 * 100000)


unsigned int randomUint()
{
  int value = rand() % UINT_MAX;
  return value;
}


unsigned int * createValueBuffer()
{
  unsigned int * valueBuffer = (unsigned int *) malloc(BUFFER_SIZE * sizeof(unsigned int));
  for (unsigned long i = 0 ; i < BUFFER_SIZE ; i++)
  {
    valueBuffer[i] = randomUint();
  }

  return (valueBuffer);
}


unsigned int * createIndexBuffer()
{
  unsigned int * indexBuffer = (unsigned int *) malloc(BUFFER_SIZE * sizeof(unsigned int));
  for (unsigned long i = 0 ; i < BUFFER_SIZE ; i++)
  {
    indexBuffer[i] = i;
  }

  return (indexBuffer);
}


unsigned long long computeSum(unsigned int * indexBuffer, unsigned int * valueBuffer)
{
  unsigned long long sum = 0;

  for (unsigned int i = 0 ; i < BUFFER_SIZE ; i++)
  {
    unsigned int index = indexBuffer[i];
    sum += valueBuffer[index];
  }

  return (sum);
}


unsigned int computeTimeInMicroSeconds()
{
  unsigned int * valueBuffer = createValueBuffer();
  unsigned int * indexBuffer = createIndexBuffer();

  struct timeval startTime, endTime;
  gettimeofday(&startTime, NULL);

  unsigned long long sum = computeSum(indexBuffer, valueBuffer);

  gettimeofday(&endTime, NULL);

  printf("Sum = %llu\n", sum);
  free(indexBuffer);
  free(valueBuffer);

  return ((endTime.tv_sec - startTime.tv_sec) * 1000 * 1000) + (endTime.tv_usec - startTime.tv_usec);

}


int main()
{
  printf("sizeof buffers = %ldMb\n", BUFFER_SIZE * sizeof(unsigned int) / (1024 * 1024));
  unsigned int timeInMicroSeconds = computeTimeInMicroSeconds();
  printf("Time: %u micro-seconds = %.3f seconds\n", timeInMicroSeconds, (double) timeInMicroSeconds / (1000 * 1000));
}

如果我启动它,我得到以下输出:

$ gcc TestPrefetch.c -O3 -o TestPrefetch && ./TestPrefetch 
sizeof buffers = 1562Mb
Sum = 439813150288855829
Time: 201172 micro-seconds = 0.201 seconds

快,快!!!据我所知(我可能错了),有这么快的程序的原因之一是,当我依次访问我的两个缓冲区时,数据可以在CPU缓存中预取。

我们可以使它更复杂,以便数据(几乎)在CPU缓存中预缓存。例如,我们可以在以下位置更改createIndexBuffer函数:

unsigned int * createIndexBuffer()
{
  unsigned int * indexBuffer = (unsigned int *) malloc(BUFFER_SIZE * sizeof(unsigned int));
  for (unsigned long i = 0 ; i < BUFFER_SIZE ; i++)
  {
    indexBuffer[i] = rand() % BUFFER_SIZE;
  }

  return (indexBuffer);
}

让我们再次尝试该程序:

$ gcc TestPrefetch.c -O3 -o TestPrefetch && ./TestPrefetch 
sizeof buffers = 1562Mb
Sum = 439835307963131237
Time: 3730387 micro-seconds = 3.730 seconds

慢了18倍多!!!

我们现在来解决我的问题。考虑到新的createIndexBuffer函数,我想使用预取加速computeSum函数

unsigned long long computeSum(unsigned int * indexBuffer, unsigned int * valueBuffer)
{
  unsigned long long sum = 0;

  for (unsigned int i = 0 ; i < BUFFER_SIZE ; i++)
  {
    __builtin_prefetch((char *) &indexBuffer[i + 1], 0, 0);
    unsigned int index = indexBuffer[i];
    sum += valueBuffer[index];
  }

  return (sum);
}

当然,我还必须更改我的createIndexBuffer,以便它分配一个包含多个元素的缓冲区

我重新启动我的计划:不是更好!由于预取可能比一个“for”循环迭代慢,所以我可能不会在之前预取一个元素,而是在之前预取两个元素

    __builtin_prefetch((char *) &indexBuffer[i + 2], 0, 0);

不是更好!两个循环迭代?不是更好吗?三个**我一直试到50岁(!!!)但是我无法提高我的函数computeSum的性能。

我能帮你理解为什么非常感谢你的帮助吗

共有3个答案

鲍飞星
2023-03-14

很抱歉我给你的不是我代码的正确版本。正确的版本是,你所说的:

__builtin_prefetch((char *) &valueBuffer[indexBuffer[i + prefetchStep]], 0, 0);

然而,即使是正确的版本,不幸的是它并没有更好

李昊苍
2023-03-14

预取通常是一个完整的缓存线。这通常是64字节。因此,随机示例总是为4字节整数获取64字节。16倍于您实际需要的数据,这非常适合减速18倍。因此,代码只受内存吞吐量而不是延迟的限制。

赵俊晤
2023-03-14

我相信上面的代码是由CPU自动优化的,没有任何空间进行手动优化。

1.主要问题是indexBuffer是按顺序访问的。硬件预取器检测到它并自动预取更多值,而无需手动调用预取。因此,在迭代i期间,值indexBuffer[i1]indexBuffer[i2],。。。已在缓存中。(顺便说一句,不需要在数组末尾添加人工元素:预取指令会自动忽略内存访问错误)。

您真正需要做的是预取valueBuffer

__builtin_prefetch((char *) &valueBuffer[indexBuffer[i + 1]], 0, 0);

2.但是在这样简单的场景中,添加上面的代码行也没有帮助。访问内存的成本是数百个周期,而添加指令是〜1个周期。您的代码已经花费了99%的时间在内存访问上。添加手动预取将使这一周期更快,也不会更好。

如果你的数学要复杂得多(试试看),比如使用一个包含大量未优化的out除法(每个20-30个循环)的表达式,或者调用一些数学函数(log,sin),那么手动预取会非常有效。

3.但即使这样也不能保证有帮助。循环迭代之间的依赖性非常弱,它仅通过sum变量实现。这允许CPU推测性地执行指令:它可能会同时开始获取valueBuffer[i],同时仍然执行valueBuffer[i]的数学运算。

 类似资料:
  • 问题内容: 我正在尝试调试使用很多指针的二进制文件。有时为了快速查看输出以找出错误,我打印了对象的地址及其对应的值,但是对象地址是随机的,这违背了快速检查的目的。有没有一种方法可以暂时/永久禁用此功能,以便每次运行程序时都获得相同的值。 哎呀。操作系统是 问题答案: 在Ubuntu上,可以使用…禁用它。 在Windows上,这篇文章可能会有所帮助… http://blog.didiersteven

  • 我有一个python程序,其中我需要加载和反序列化一个1GB的pickle文件。它需要一个良好的20秒,我想有一个机制,使腌菜的内容是随时可用的使用。我看过shared_memory,但是所有使用它的例子似乎都涉及到numpy,而我的项目没有使用numpy。使用或其他方式实现此目标的最简单、最干净的方法是什么? 这就是我现在(每次运行时)加载数据的方式: 我一直在使用,但对于一个包含许多文件的大型

  • 问题内容: 是否有Python文件类型可用于访问随机行而不遍历整个文件?我需要在一个大文件中搜索,无法将整个内容读取到内存中。 任何类型或方法将不胜感激。 问题答案: 这似乎只是设计的目的。一个对象为文件创建一个类似于字符串的接口: 如果您想知道,还可以将对象分配给:

  • 问题内容: 允许从向量中进行加权选择,即 选择概率为0.2的1,概率为0.5的2和概率为0.3的3。 如果我们想对每个行都是概率向量的2D数组(矩阵)以向量化的方式快速进行操作,该怎么办?也就是说,我们想要一个来自随机矩阵的选择向量吗?这是超级慢的方式: : 这篇文章表明,并且可能是一种潜在的方法,而且很快。但是虽然可以沿numpy数组的一个轴执行此操作,但是该函数一次只能在单个数组上运行。同样,

  • 给定一个随机数生成器random(7),它可以以相等的概率生成数字1,2,3,4,5,6,7(即每个数字出现的概率为1/7)。现在我们要设计一个随机数(5),它能以相等的概率(1/5)生成1,2,3,4,5。 有一种方法:每次我们随机运行(7),只有当它生成1-5时才返回。如果是6或7,再运行一次,直到它是1-5。 我有点困惑。第一个问题是: 如何用数学方法证明每个数字发生的概率是1/5?例如,假

  • 前面介绍了生成顺序访问文件和从顺序访问文件搜索特定信息。顺序访问文件不适合快速访问应用程序,即要立即找到特定记录的信息。快速访问应用程序的例子有航空订票系统、银行系统、销售网点系统、自动柜员机和其他要求快速处理特定数据的事务处理系统(transaction processing system)。银行要面对成千上万的客户,但自动柜员机能在瞬间作出响应。这种快速访问应用程序是用随机访问文件(rando