- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
已更新,行下方的原始问题:
我需要计算中位数,并想使用 O(N) 快速选择算法。然而事实证明,当数组不再是 double 平面数组,而是结构数组(其中一个元素是用于中值计算的元素)时,运行时间不再与 O(N) 成比例。
以下平面阵列版本具有近似线性的运行时间:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define SWAP(a,b) temp=(a);(a)=(b);(b)=temp;
double quickselect(unsigned long k, unsigned long n, double *arr)
{
unsigned long i, ir, j, l, mid;
double a, temp;
l=1;
ir=n-1;
for (;;) {
if (ir <= l+1) {
if (ir == l+1 && arr[ir] < arr[l]) {
SWAP(arr[l],arr[ir])
}
return arr[k];
} else {
mid=(l+ir) >> 1;
SWAP(arr[mid],arr[l+1])
if (arr[l] > arr[ir]) {
SWAP(arr[l],arr[ir])
}
if (arr[l+1] > arr[ir]) {
SWAP(arr[l+1],arr[ir])
}
if (arr[l] > arr[l+1]) {
SWAP(arr[l],arr[l+1])
}
i=l+1;
j=ir;
a=arr[l+1];
for (;;) {
do i++; while (arr[i] < a);
do j--; while (arr[j] > a);
if (j < i) break;
SWAP(arr[i],arr[j])
}
arr[l+1]=arr[j];
arr[j]=a;
if (j >= k) ir=j-1;
if (j <= k) l=i;
}
}
}
int main()
{
unsigned long i, j, k, l, m;
unsigned long ntest = 1e2;
unsigned long N[5] = {1e3, 1e4, 1e5, 1e6, 1e7};
clock_t start, diff;
int seed = 215342512; //time(NULL);
srand(seed);
double *arr = (double*) malloc(N[4] * sizeof(double));
for (i=0; i<5; i++)
{
start = clock();
for (j=0; j<ntest; j++)
{
for (k=0; k<N[i]; k++)
{
arr[k] = (double) rand() / (double) RAND_MAX;
}
quickselect(N[i] / 2, N[i], arr);
}
diff = clock() - start;
printf("%lu %.5f\n", N[i], (double) diff / CLOCKS_PER_SEC);
}
}
给予:
1000 0.00228
10000 0.02014
100000 0.19868
1000000 2.01272
10000000 20.41286
但是以下带有结构的版本具有非线性运行时间:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define SWAP(a,b) temp=(a);(a)=(b);(b)=temp;
typedef struct {
double x;
double y;
double z;
int id;
} point_t;
point_t* quickselect(unsigned long k, unsigned long n, point_t **arr)
{
unsigned long i, ir, j, l, mid;
point_t *a, *temp;
l=1;
ir=n-1;
for (;;) {
if (ir <= l+1) {
if (ir == l+1 && arr[ir]->x < arr[l]->x) {
SWAP(arr[l],arr[ir])
}
return arr[k];
} else {
mid=(l+ir) >> 1;
SWAP(arr[mid],arr[l+1])
if (arr[l]->x > arr[ir]->x) {
SWAP(arr[l],arr[ir])
}
if (arr[l+1]->x > arr[ir]->x) {
SWAP(arr[l+1],arr[ir])
}
if (arr[l]->x > arr[l+1]->x) {
SWAP(arr[l],arr[l+1])
}
i=l+1;
j=ir;
a=arr[l+1];
for (;;) {
do i++; while (arr[i]->x < a->x);
do j--; while (arr[j]->x > a->x);
if (j < i) break;
SWAP(arr[i],arr[j])
}
arr[l+1]=arr[j];
arr[j]=a;
if (j >= k) ir=j-1;
if (j <= k) l=i;
}
}
}
int main()
{
unsigned long i, j, k, l, m;
unsigned long ntest = 1e2;
unsigned long N[5] = {1e3, 1e4, 1e5, 1e6, 1e7};
clock_t start, diff;
int seed = 215342512; //time(NULL);
srand(seed);
point_t **ap, *a;
ap = (point_t**) malloc(N[4] * sizeof(point_t*));
if (ap == NULL) printf("Error in ap\n");
a = (point_t*) malloc(N[4] * sizeof(point_t));
if (a == NULL) printf("Error in a\n");
for (i=0; i<N[4]; i++)
{
ap[i] = a+i;
}
for (i=0; i<5; i++)
{
start = clock();
for (j=0; j<ntest; j++)
{
for (k=0; k<N[i]; k++)
{
ap[k]->x = (double) rand() / (double) RAND_MAX;
}
quickselect(N[i] / 2, N[i], ap);
}
diff = clock() - start;
printf("%lu %.5f\n", N[i], (double) diff / CLOCKS_PER_SEC);
}
}
给予:
1000 0.00224
10000 0.02587
100000 0.37574
1000000 7.18962
10000000 96.34863
两个版本都是用 gcc -O2 编译的(但 -O0 给出了相同的缩放比例)。
这种缩放变化从何而来,如何解决?
请注意,虽然我可以更改结构,但我不能只更改中位数 y
因为我还需要知道与中点对应的其他参数。此外,我需要结果数组的快速选择行为(例如 a.y <= m.y
用于 a
左侧的所有 m
和 b.y > m.y
用于 b
右侧的所有 m
)。
我需要计算中位数,并想使用 O(N) 快速选择算法。然而事实证明,当数组不再是 double 平面数组,而是结构数组(其中一个元素是用于中值计算的元素)时,运行时间不再与 O(N) 成比例。
我使用以下实现:
#define SWAP(a,b) temp=(a); (a)=(b); (b)=temp;
typedef struct point_t point_t;
struct point_t {
double y;
// unsigned long something;
//
// double *something_else;
//
// double yet_another thing;
//
// point_t* again_something;
};
void median(point_t *arr, unsigned long n)
{
unsigned long k = n / 2;
unsigned long i, ir, j, l, mid;
point_t a, temp;
l=0;
ir=n-1;
for (;;)
{
if (ir <= l+1)
{
if (ir == l+1 && arr[ir].y < arr[l].y)
{
SWAP(arr[l], arr[ir])
}
return arr + k;
}
else
{
mid = (l + ir) >> 1;
SWAP(arr[mid], arr[l+1])
if (arr[l].y > arr[ir].y)
{
SWAP(arr[l], arr[ir])
}
if (arr[l+1].y > arr[ir].y)
{
SWAP(arr[l+1], arr[ir])
}
if (arr[l].y > arr[l+1].y)
{
SWAP(arr[l], arr[l+1])
}
i = l+1;
j = ir;
a = arr[l+1];
for (;;)
{
do i++; while (arr[i].y < a.y);
do j--; while (arr[j].y > a.y);
if (j < i) break;
SWAP(arr[i], arr[j])
}
arr[l+1] = arr[j];
arr[j] = a;
if (j >= k) ir = j-1;
if (j <= k) l = i;
}
}
}
与 -O2
结构被优化掉了(我认为,至少缩放看起来与普通数组相同)并且缩放是线性的。但是,当取消注释该结构的其他组件时,缩放比例不再是线性的。怎么会这样?如何解决这个问题?
请注意,虽然我可以更改结构,但我不能只更改中位数 y
因为我还需要知道与中点对应的其他参数。此外,我需要结果数组的快速选择行为(例如 a.y <= m.y
用于 a
左侧的所有 m
和 b.y > m.y
用于 b
右侧的所有 m
)。
最佳答案
我认为内存缓存错误可以解释执行时间的非线性增长。在我的 x86_64 架构 PC (Linux + gcc) 中,sizeof(double)
为 8 而 sizeof(point_t)
为 32,因此适合缓存内存的元素更少。但非线性增长的更大原因是通过代码中的指针数组对 point_t
结构的内存访问将很快高度随机化,因此会发生越来越多的缓存未命中...
我修改了如下代码:
--- test2.c
+++ test3.c
-80,14 +80,12
if (a == NULL)
printf("Error in an");
- for (i = 0; i < N[4]; i++) {
- ap[i] = a + i;
- }
-
for (i = 0; i < 5; i++) {
start = clock();
for (j = 0; j < ntest; j++) {
for (k = 0; k < N[i]; k++) {
+ ap[k] = a + k;
ap[k]->x = (double) rand() / (double) RAND_MAX;
}
并且执行时间增长更加线性。
带有 double
数组的原始 quicselect()
:
1000 0.00000
10000 0.04000
100000 0.22000
1000000 1.98000
10000000 20.73000
带有 point_t *
数组的原始 quickselect()
:
1000 0.01000
10000 0.02000
100000 0.71000
1000000 8.64000
10000000 157.77000
与上面的 point_t *
数组完全相同的 quickselect()
,但在调用 quickselect() 之前确保数组中的指针按顺序排列
通过应用上面的补丁:
1000 0.00000
10000 0.02000
100000 0.40000
1000000 4.71000
10000000 49.80000
请注意,即使修改后的版本在计时循环中进行了额外的排序,它仍然更快。
我正在运行 3.2GHz Pentium(R) 双核 CPU E6700、64 位 Linux、gcc 4.6、优化 -O2
。 (我的机器没有闲置,所以我的基准数据有一些波动 - 如果我要进行更严格的基准计算,我也会考虑使用 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...)
来提高 Linux 系统的准确性排除内核未调度基准进程运行的时间。)
更新:例如,valgrind
(如果您的平台支持)可用于分析缓存命中的影响。我修改了程序以接受两个参数,数组大小(对应于数组 N[]
的元素)和测试计数(对应于 ntest
)。没有 valgrind
的执行时间,其中 test2
本质上是问题中列出的未修改程序,test4
是重新排列 ap 的修改版本[]
调用quickselect()
函数前的数组:
bash$ ./test2 10000000 100
Array of 10000000 elements, 100 times: 154.40000 seconds
bash$ ./test4 10000000 100
Array of 10000000 elements, 100 times: 48.45000 seconds
这是使用 cachegrind 工具运行 valgrind
的结果:
bash$ valgrind --tool=cachegrind ./test2 10000000 100
==23563== Cachegrind, a cache and branch-prediction profiler
==23563== Copyright (C) 2002-2010, and GNU GPL'd, by Nicholas Nethercote et al.
==23563== Using Valgrind-3.6.1-Debian and LibVEX; rerun with -h for copyright info
==23563== Command: ./test2 10000000 100
==23563==
Array of 10000000 elements, 100 times: 1190.24000 seconds
==23563==
==23563== I refs: 80,075,384,594
==23563== I1 misses: 1,091
==23563== LLi misses: 1,087
==23563== I1 miss rate: 0.00%
==23563== LLi miss rate: 0.00%
==23563==
==23563== D refs: 36,670,292,139 (25,550,518,762 rd + 11,119,773,377 wr)
==23563== D1 misses: 4,218,722,190 ( 3,223,975,942 rd + 994,746,248 wr)
==23563== LLd misses: 4,190,889,241 ( 3,198,934,125 rd + 991,955,116 wr)
==23563== D1 miss rate: 11.5% ( 12.6% + 8.9% )
==23563== LLd miss rate: 11.4% ( 12.5% + 8.9% )
==23563==
==23563== LL refs: 4,218,723,281 ( 3,223,977,033 rd + 994,746,248 wr)
==23563== LL misses: 4,190,890,328 ( 3,198,935,212 rd + 991,955,116 wr)
==23563== LL miss rate: 3.5% ( 3.0% + 8.9% )
和
bash$ valgrind --tool=cachegrind ./test4 10000000 100
==24436== Cachegrind, a cache and branch-prediction profiler
==24436== Copyright (C) 2002-2010, and GNU GPL'd, by Nicholas Nethercote et al.
==24436== Using Valgrind-3.6.1-Debian and LibVEX; rerun with -h for copyright info
==24436== Command: ./test4 10000000 100
==24436==
Array of 10000000 elements, 100 times: 862.89000 seconds
==24436==
==24436== I refs: 82,985,384,485
==24436== I1 misses: 1,093
==24436== LLi misses: 1,089
==24436== I1 miss rate: 0.00%
==24436== LLi miss rate: 0.00%
==24436==
==24436== D refs: 36,640,292,192 (24,530,518,829 rd + 12,109,773,363 wr)
==24436== D1 misses: 2,814,232,350 ( 2,189,229,679 rd + 625,002,671 wr)
==24436== LLd misses: 2,796,287,872 ( 2,171,294,250 rd + 624,993,622 wr)
==24436== D1 miss rate: 7.6% ( 8.9% + 5.1% )
==24436== LLd miss rate: 7.6% ( 8.8% + 5.1% )
==24436==
==24436== LL refs: 2,814,233,443 ( 2,189,230,772 rd + 625,002,671 wr)
==24436== LL misses: 2,796,288,961 ( 2,171,295,339 rd + 624,993,622 wr)
==24436== LL miss rate: 2.3% ( 2.0% + 5.1% )
参见 Valgrind manual如何阅读这些统计数据。一个要点是:“在现代机器上,L1 未命中通常会花费大约 10 个周期,而 LL 未命中可能花费多达 200 个周期”。计算两种情况之间的 LLd(最后一级数据 缓存)错误成本差异(每个错误差异乘以 3.2GHz CPU 每 3.2e9 周期/秒假设的 200 个周期)给出
bash$ echo $(( (4190889241 - 2796287872) * 200 / 3200000000 )) seconds
87 seconds
D1 失误在这里贡献很小(如果 D1 失误成本与 LLd 失误成本无关,则总计 91 秒);由于我们所有的错误(最值得注意的是这台计算机中 LLd 错误的实际成本),D1 错误可以忽略不计。
test2
和 test4
的运行时间差异约为 106 秒,与上述 86 秒相当接近。这一切都可以变得更准确,但这似乎已经证明了缓存未命中在测试安排中的影响。
附言valgrind
写了一个日志文件,我可以在其中验证它似乎正确检测了 L2 和 L1 缓存大小和类型。
关于c - 具有结构数组的快速选择具有非线性运行时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8476184/
如何更改循环中变量的名称?比如 number1 、 number2 、 number3 、 number4 ? var array = [2,4,6,8] func ap ( number1: Int
我想设置 View 的背景颜色并在一定延迟后将其更改为另一种颜色。这是我的尝试方式: print("setting color 1") self.view.backgroundColor = UICo
我在使用 express-session 时遇到问题。 session 数据不会在请求之间持续存在。 正如您在下面的代码中看到的那样,/join 路由设置了一些 session 属性,但是当 /sur
我试图从叶渲染器获得一个非常简单的结果,用于快速 Steam 的 for 循环。 我正在上传叶文件 HTML,因为它不接受此处格式正确的代码 - 下面的pizza.swift代码- import
你们中有人有什么好的链接可以与我分享吗?我正在寻找一个 FAST 程序员编辑器,它可以非常快速地打开包含超过 100, 000 行代码的文件?我目前正在使用记事本自动取款机,打开一个 29000 行长
我现在正在处理眼动追踪数据,因此拥有一个巨大的数据集(想想数百万行),因此希望有一种快速的方法来完成此任务。这是它的简化版本。 数据告诉您眼睛在每个时间点正在查看的位置以及我们正在查看的每个文件。 X
我是新手,想为计时器或其他设备选择提示音。 如何打开此列表,以选择其中一种声音? Alert sound list 最佳答案 您将无法在应用中使用系统声音。 但是,您可以包括自己的声音文件,并将其显示
我编写了以下代码来构建具有顺序字符串的数组。 它的工作方式与我预期的一样,但我希望它能更快地运行。有没有更有效的方法在PowerShell中产生我想要的结果? 我是PowerShell的新手,非常感谢
我有一个包含一些非唯一行的矩阵,例如: x 尝试 y <- rle(apply(x, 1, paste, collapse = " ")) # y$lengths is the vector con
我的函数“keyboardWillShown”有问题。所以我想要的是菜单打开时,菜单正好出现在键盘上方。它可以在Iphone 8 plus,8、7、6上完美运行。但是,当我在模拟器上运行Iphone
我正在尝试通过Swift 5中的HTTP get方法从API提取数据。它在启动时成功加载了数据,但是当我刷新页面时,它说“索引超出范围”,这是因为数据是不再会在我的日志中读取,因此索引中没有任何内容。
我想做什么: 从我的数据库中获取时间戳并将其转换为用户的时区。 我的代码: let tryItNow = "\(model.timestampName)" let format = D
给定字体名称和字体大小,如何查找字符串的宽度(CGFloat)? (目标是将UIView的宽度设置为足以容纳字符串的宽度。) 我有两个字符串:一个重复“1”,重复36次,另一个重复“M”,重复36次。
我正在尝试解析此JSON ["Items": ( { AccountBalance = 0; AlphabetType = 3; Description = "\U0631\U
我在UINavigationBar内放置了一个UILabel。 我想根据navigationBar的高度增加该标签的字体大小。当navigationBar很大时,我希望字体大小更大;当滚动并缩小nav
我想将用户输入限制为仅有效数字并使用以下内容: func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, rep
目前我有一个包含超过 100.000 张图像的数据库,它们大小不一或类似,但我想为我的公司制作以下内容: 我插入/上传一张图片,系统返回最有可能相同的图片。我不知道使用什么算法,但它需要快速。我可以预
在我的 swift 项目中,我有一个按钮,我想在标签上打印按下该按钮的时间。 如何解决这个问题? 最佳答案 添加到DHEERAJ的答案中,您只需在func press(sender: UIButton
我必须发表评论,尝试在解析中导入数组。然而,有一个问题。 当我尝试从 Parse 加载数组时,我的输出是 ("Blah","Blah","Blah")这是一个元组...而不是一个数组 TT... 如何
我的应用程序有一个名为 MyDevice 的类,我用它来与硬件通信。该硬件是可选的,实例变量也是可选的: var theDevice:MyDevice = nil 然后,在应用程序中,我必须初始化设备
我是一名优秀的程序员,十分优秀!