哪个更快:适当的数据输入还是适当的数据结构?

时间:2010-05-20 14:27:12

标签: c performance

我有一个数据集,其列如下所示:

Consumer ID | Product ID | Time Period | Product Score
1           | 1          | 1           | 2
2           | 1          | 2           | 3

等等。

作为计划的一部分(用C语言编写),我需要处理所有消费者针对特定产品和时间段组合给出的所有可能组合的产品分数。假设有3个产品和2个时间段。然后我需要处理所有可能组合的产品分数,如下所示:

Product ID | Time Period 
1          | 1
1          | 2
2          | 1
2          | 2
3          | 1
3          | 2

我将需要多次处理上述行的数据(> 10k)并且数据集相当大(例如,48k消费者,100个产品,24个时间段等)。所以速度是一个问题。

我提出了两种处理数据的方法,我想知道哪种方法更快,或者可能无关紧要? (速度很重要但不是以过度维护/可读性为代价):

  1. 对产品ID和时间段的数据进行排序,然后遍历数据以提取所有可能组合的数据。

  2. 存储为产品ID和时间段的特定组合提供产品分数的所有消费者的消费者ID,并相应地处理数据。

  3. 有什么想法?还有其他方法可以加快处理速度吗?感谢

7 个答案:

答案 0 :(得分:3)

与许多与性能相关的问题一样,唯一真实,明确的答案是编写基准。速度将取决于许多事情,并且听起来并不像是在谈论线性算法与二次算法的直接情况(甚至会对输入大小产生额外的依赖性)。

因此,实现两种方法,在样本数据上运行它们,并对结果计时。这将比我们试图在信息有限的情况下解决这个问题更加快速和更具决定性。

答案 1 :(得分:0)

我建议过滤数据,如第二步,然后按照第一步进行处理。如果您的表现不可接受,请调整性能。为您的基线设置一些基准,然后尝试一些不同的方法。

在大多数现实世界中,我建议不要仅仅为了进行基准测试而实施多种方法。表现可能类似。如果它不相似,它可能表现不佳,并且显然需要调整。您可以更好地花时间实现其他功能。

答案 2 :(得分:0)

这会产生一个小的数据库表。如果存在完整的消费者/产品/时间矩阵,那么它的数据大约为0.4GB。您是否考虑过在SQL中运行整个事情?即使你没有我们一个完整的数据库;对于那么大小的数据,为每个可能的排序顺序生成一个完整的表并将每个转储到一个文件是很实际的。然后,您可以按照您选择的顺序加载所需的任何文件。

此外,如果您可以并行运行完整的10K传递或每次传递至少几十次传递,您可能会提前执行此操作,因为它可能会大大减少您的IO等待和/或缓存未命中。

答案 3 :(得分:0)

实际上这两种方法与我非常相似。为了存储为特定组合提供分数的所有客户的客户ID,您需要对数据进行排序或执行更昂贵的操作。

你可以换空间吗?如果是,则不进行预处理,但创建所有组合的数组(10x24)以存储分数。处理数据并更新特定组合的分数。如果您需要平均分数,请同时存储提供分数的客户总数和数量。

答案 4 :(得分:0)

你有任何影响力的最慢部分可能是复制大块的记忆。因此,应用的第一种技术是将每行的值放在结构中,并仅通过指针引用它,直到所有处理完成为止。结构将类似于:

typedef struct {
 int consumer;
 int product;
 int time;
 int score;
} rowData;

在此基础上,我认为您最好循环输入行并构建由消费者和产品标识的结构的二叉树(或其他排序结构),并包含指针的查找表到所有匹配的rowData:

typedef struct {
 int consumer;
 int product;
 rowData * matches;
} matchLut;

一旦所有行都放在树上的查找表中,就可以处理每个包。

答案 5 :(得分:0)

如果内存允许将数据存储在二维数组中(实际上是3d,但我稍后会介绍)。该数组将被(product_id,time_period)索引。

如果您对数据的处理允许它,则2D数组的每个元素都可以是新数据的累加器,因此您读入数据元素,然后调整2D数组的相应元素以反映它。如果此方法有效,则在完成阅读后将处理您的数据。

如果您的处理要求您同时拥有每个数据元素的数据,那么您可以将2D数组的每个元素都列为一个列表(这是第3个D)。如果您不知道每个客户条目的数量(product_id,time_period),它可以是可变长度列表。读入数据后,您需要重新访问2D数组的每个元素以处理每个列表。如何安排阵列以及如何访问元素对性能至关重要。 你可能想要动态地声明它,但对于这个例子

struct element_t element[NUMBER_OF_PRODUCTS][NUMBER_OF_TIME_PERIODS];
// don't forget to initialize these elements to empty
...
for (p = max_product_id; p >= 0; p--) {
    for (t = max_time_period; t >= 0; t--) {
         process(element[p][t]);
    }
}

如果您想要在移动到下一个产品之前处理每个产品,那么效果会更好。如果要在移动到下一个时间段之前处理每个时间段(针对所有产品),您可以交换声明的行,列和循环以实现更好的缓存命中。

您应该注意,这会为您排序,而不会说“对此数据进行排序”。

如果内存不允许,那么您可能希望在读取数据时将部分数据存储到文件中。这与上面提到的数组/循环组织/缓存命中优化具有相同的问题,但它会被放大了很多次。在读取主数据结束时,您希望能够处理来自特定临时文件的所有数据(可能包含给定产品的所有数据(给定时间段内的xOR)),然后再移动到下一个。尝试这样做的主要不好的一点是,当您在读取数据时,您很可能必须处理无法同时打开每个临时文件的问题。这可能需要您提出一种进行打开文件交换的方法(与内存交换相同,除了您交换的是打开文件而不是内存页)。不过,这将是另一个问题。

答案 6 :(得分:0)

我建议您根据最常访问的流程重新排序数据。访问最频繁的数据应该是最简单和最快速的。

另外,请查看Database Normalization。这是一种组织数据以实现最少量复制的概念,也使得访问数据更加高效。

另一个想法是使用索引进行不太流行的数据搜索。