程序并行配置不正确(软件并行配置有问题)

前沿拓展:

程序并行配置不正确

你不小心卸载了VC++ 2008,到天空软件或者某某卫士的软件管理里面下载个VC 2008 安装即可。

它不但是一款程序开发工具,也是有些软件需要的运行环境。


代码的优化特别是与循环相关的代码的优化需要考虑计算机系统的各个层次,包括底层CPU的并行处理能力,存储的缓存机制,编译器的优化能力,程序员需要充分创造在CPU、编译器优化时需要具备的条件,同时,需要考虑适当的数据结构和算法。

1 减少循环中函数调用

1.1 增加了函数调用的版本

#include <stdio.h>
size_t strlen(char* str);
void lower(char *str)
{
for(size_t i=0; i<strlen(str); i++)
if(str[i] >= 'A' && str[i] <= 'Z')
str[i] += ('a'-'A');
}
int main()
{
char str[] = "abcABCaBc";
printf("%s\n",str);
lower(str);
printf("%s\n",str);
getchar();
return 0;
}
size_t strlen(char* str)
{
if(str==NULL)
return 0;
char* pm = str;
while(*pm++);
return pm-str-1;
}

1.2 减少了函数调用的版本

void lower(char *str)
{
size_t len = strlen(str);
for(size_t i=0; i<len; i++)
if(str[i] >= 'A' && str[i] <= 'Z')
str[i] += ('a'-'A');
}

1.3 可以使用位运算来优化函数体

void lower(char *str)
{
size_t len = strlen(str);
for(size_t i=0; i<len; i++)
str[i] |= 1<<5;
}

strlen()在GNU C Library中有更高效率但有变态的写法:

https://code.woboq.org/userspace/glibc/string/strlen.c.html

3 其它与循环相关的优化

3.1 循环中数组的行序和列序访问对性能产生的影响

程序并行配置不正确(软件并行配置有问题)

函数sum_array_rows的效率要高一些,为什么呢?

如果看汇编代码,两者产生的汇编指令是一致的。

二者运行的效率差异主要来自于“缓存命中率”。

C语言编译对于二维数组,以行序优先的顺序来翻译,存储时,先存储第一行、然后是第二行,第三行……

计算机的内存是线性结构顺序存储的。

计算机CPU一般都有相对内存速度更快的缓存(称为缓存线(cache line),64个字节),CPU读取数据会一次从顺序存储的内存中读取64个字节到缓存。并且CPU在加载缓存线数据的时间内,能并行处理相当多的工作。

当访问a[i][j]时,需要先将数据读取到寄存器,CPU会先到缓存中去读取,缓存中没有才到内存中去读取。寄存器的速度最快,其次是缓存、内存、硬盘。

由此,连续操作多维数组的最后一个维度最快(最后一个维度的数据是连续存储的),可以获得最大概率的“缓存命中率”。

内循环中的a[i][j]是连续操作最后一个维度,是按照内存线性结构顺序存储来访问的,所以效率最高。这也解释了要将双重循环中将长循环写到内循环。

内循环中a[i][j]操作时,一次加载缓存64个字节(32位平台则是16个整数),则最多可连续命中缓存16次。因为a[i][j]访问时,i是外循环的行,j是内循环的列,按行连续地读取每一的数据(参考上图),缓存命中率高。

循环中a[i][j]操作时,一次加载缓存64个字节,16个整数,如果数组列数是16,则最多命中一次,如果是8列,最多命中两次。因为a[j][i]访问时,i是外循环的行,j是内循环的列,按列间断地读取每一的数据(参考上图),缓存命中率低。

3.2 循环中消除内存引用、循环展开、提高并行度

#include <stdio.h> // 《深入理解计算机系统》循环代码优化
#include <stdlib.h>
#include <time.h>
/*
* 程序优化的第一步就是消除不必要的工作,让代码尽可能有效地执行所期望的任务。这包括消除不
* 必要的函数调用、条件测试和内存引用。这些优化不依赖目标机器的任何具体属性。了解了处理器
* 的运作,就可以进行程序优化的第二步,利用处理器提供的指令级并行能力,同时执行多条指令。

* 为了清晰地说明如何去优化一个程序,这里使用一个如下向量数据结构的运行示例。*/

#define data_t int
typedef struct { /* 创建一个vector抽象数据类型 */
long len; /* 长度 */
data_t *data; /* 动态内存起始地址 */
}vec_rec, *vec_ptr;

/* 创建一个指定长度的vector */
vec_ptr new_vec(long len) /* 为头部结构体分配内存 */
{
vec_ptr result = (vec_ptr) malloc(sizeof(vec_rec));
data_t *data = NULL;
if (!result)
return NULL; /* 头部创建失败 */
result->len = len;
if (len > 0)
{
data = (data_t*) calloc(len, sizeof(data_t));
if(!data)
{
free((void*) result);
return NULL; /* 存储空间分配失败 */
}
}
result->data = data;
return result;
}
/* 取vector元素并存在目标位置 */
/* 越界返回0,成功返回1 */
int get_vec_element(vec_ptr v, long index, data_t *dest)
{
if(index < 0 || index >= v->len)
return 0;
*dest = v->data[index];
return 1;
}

long vec_length(vec_ptr v) /* 返回vector的长度 */
{
return v->len;
}

void combine_add0(vec_ptr v, data_t *dest) /* 求向量元素和 低效率版本*/
{
long i;
*dest = 0;
for (i = 0; i < vec_length(v); i++)
{
data_t val;
get_vec_element(v, i, &val);
*dest = *dest + val; /* +可以是*或用一个宏OP代替 */
}
}
/*
这个函数的功能非常简单,无非就是将传入的向量指针v所指向的vector头对应的各个元素相加,
每加上一个元素,将其结果保存在*dest中。

可以观察到,在求向量元素和的过程中combine_add0函数频繁调用了函数vec_length作为for循环
的测试条件。向量的长度并不会随着循环的进行而改变,但是在这一段代码中每一次循环都需要对
测试条件求值,这就做了很多的重复的无意义的工作。所以这里修改了另外一个版本combine_add
2,只计算一次向量长度,保存在length中,以提升程序运行的效率。
*/

// 1 减少循环中的函数调用1
void combine_add1(vec_ptr v, data_t *dest) /* 向量元素求和 */
{
long i;
long length = vec_length(v);

*dest = 0;
for (i = 0; i < length; i++)
{
data_t val;
get_vec_element(v, i, &val);
*dest = *dest + val;
}
}
/*
这个优化叫做代码移动。这类优化主要解决那些需要执行多次但每次计算结果不会改变的计算。编
译器会试着进行代码移动以优化程序,但是对于有些函数来说,调用次数不同或者调用位置不同会
导致调用结果也有不同,如一个计算函数自身被调用次数的函数。编译器不能可靠的发现对某些函
数进行代码移动是否会带来负面影响,那么它就不会对这段代码进行优化。为了改进代码,程序员
必须经常帮助编译器显示地完成代码移动。
*/

// 2 减少循环中的函数调用2

/*
函数调用会带来时间开销。在combine_add1的代码中,每次循环迭代都会调用get_vec_element来
获取下一个元素。每次调用这个函数都会把索引i与边界做比较,这就会造成程序运行效率低。这
里增加一个函数get_vec_element,用于返回向量的首个元素的地址:
*/

data_t *get_vec_start(vec_ptr v)
{
return v->data;
}

void combine_add2(vec_ptr v, data_t *dest)/* 直接通过数组访问vector元素 */
{
long i;
long length = vec_length(v);
data_t *data = get_vec_start(v); /* v->data */

*dest = 0;
for (i = 0; i < length; i++)
{
*dest = *dest + data[i];
}
}
/*
书上在这里给出的结论是,性能并没有明显的提升,甚至求和的性能还略有下降。看来在循环调用
中,还存在着其他因素限制了程序的运行效率。那么是什么原因导致的呢?

分析:现代处理器以指令流水线化的方式工作,当遇到分支的时候,处理器猜测分支该往哪个方向
走。处理器会继续执行其预测的分支指令,分支结果确定之前,处理器会避免修改寄存器或内存值
。如果预测正确,那么处理器就会提交执行的指令的结果,将其存储到寄存器或者内存中。如果预
测错误,处理器必须丢掉这次投机执行的结果,并且跳转到正确位置重新开始取指令的过程,重新
填充指令流水线。但是,这并不意味着所有的程序分支都会减缓程序的执行。在现代处理器中,分
支预测的逻辑非常善于辨别不同的分支指令的长期趋势。例如在循环中,结束循环的分支通常被预
测为选择分支,因此只有最后一次会导致预测错误。而在本例中,检测总是确定索引是在界内的,
只有最后一次i越界了才会预测失败,是高度可预测的。所以代码避免边界比较并没有带来明显的
效率提升。

不是所有的检测都是高度可以测的,有时候甚至需要程序员编写好的代码来引导程序被翻译成尽可
能可预测的汇编代码。这需要一些试验,写出函数的不同版本,然后检查产生的汇编代码,并测试
性能。故而这依然是代码优化的重要步骤,这些步骤将最终产生显著的性能提升。
*/

// 3 消除循环中不必要的内存引用
/*
combine_add3的代码将累加值存放在dest这个指针所指向的位置,下图是编译器编译出来的汇编代
码:
*dest = *dest + data[i];
0040E053 mov eax,dword ptr [ebp+0Ch] // *dest读(循环体内)
0040E056 mov ecx,dword ptr [eax]
0040E058 mov edx,dword ptr [ebp-4]
0040E05B mov eax,dword ptr [ebp-0Ch]
0040E05E add ecx,dword ptr [eax+edx*4]
0040E061 mov edx,dword ptr [ebp+0Ch] // *dest写(循环体内)
0040E064 mov dword ptr [edx],ecx
可以看到在这个循环中,每次都要从dest指向的内存读出对应的数值,然后把新值又写入内存。考
虑到每次读取的值都是上一次循环写入的值,所以这样的读写非常浪费时间。为了消除这种无意的
内存读写,将代码重写,引入临时变量acc,用于累计计算结果,在循环执行完毕后再讲acc的值写
入内存。
*/

void combine_add3(vec_ptr v, data_t *dest)/* 累加结果存放在局部变量当中 */
{
long i;
long length = vec_length(v);
data_t *data = get_vec_start(v); /* v->data */
data_t acc = 0;

for (i = 0; i < length; i++)
{
acc = acc + data[i];
}
*dest = acc;
}

/*
161: acc = acc + data[i];
0040E0D1 mov edx,dword ptr [ebp-4]
0040E0D4 mov eax,dword ptr [ebp-0Ch]
0040E0D7 mov ecx,dword ptr [ebp-10h]
0040E0DA add ecx,dword ptr [eax+edx*4]
0040E0DD mov dword ptr [ebp-10h],ecx
162: }
0040E0E0 jmp combine_add4+40h (0040e0c0)
163: *dest = acc;
0040E0E2 mov edx,dword ptr [ebp+0Ch] // *dest只有一次写(循环体外)
0040E0E5 mov eax,dword ptr [ebp-10h]
0040E0E8 mov dword ptr [edx],eax
*/
/* 减少了大量的内存读写,程序性能得到了极大的提高。 */

// 4 循环展开

/* 循环展开是一种程序变换,通过增加每次迭代计算的元素数量,减少循环的迭代次数。循环按
照因子k展开,即是说每次循环索引增加k。这里展示一个k=2时的循环展开版本: */

/* 按照因子k = 2展开的版本 */
void combine_add4(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length – 1;
data_t *data = get_vec_start(v); /* v->data */
data_t acc = 0;

for (i = 0; i < limit; i+=2) /* 一次循环累加两个数 */
{
acc = (acc + data[i]) + data[i + 1];
}

for (; i < length; i++) /* 完成剩下的数的累加 */
{
acc = acc + data[i];
}
*dest = acc;
}
/*
在本例中,由于减少了循环开销操作,所以性能有所提升。不过将求累和改为求累积的话,性能并
不能有所提升。其原因在于求乘积的运算占用的时钟数比求和要长,导致在循环执行的过程中性能
瓶颈主要在于求乘积而非循环开销。这说明具体问题还得具体分析,并不是说循环扩展就一定能够
带来性能提升。书中给出了k取不同值以及采用其他combine方法下性能的结果对比。 */

// 5 提高并行性

/*
在前面的五种优化方法中,程序都存在一个问题,即把累加值放在一个单独的变量acc中
。在计算完成之前都不能计算acc的新值。为什么不设置多个acc变量,同时计算多个acc值呢?这
里给出展开次数k = 2,即采用两个acc变量的程序版本: */

void combine_add5(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length – 1;
data_t *data = get_vec_start(v); /* v->data */
data_t acc0 = 0;
data_t acc1 = 0;

for (i = 0; i < limit; i+=2) /* 一次处理两个元素 */
{
acc0 = acc0 + data[i];
acc1 = acc1 + data[i + 1];
}

for (; i < length; i++) /* 完成所有的数累加 */
{
acc0 = acc0 + data[i];
}
*dest = acc0 + acc1;
}
/*
因为程序的性能受运算单元延迟限制,执行加法的功能单元是完全流水线化的,这意味着每个时钟
周期可以开始一个新的操作,并且有些操作可以由多个运算单元执行。硬件具有更高的加法和乘法
效率,所以程序员在编写代码的时候也应该尽可能使得代码能够向硬件的效率靠拢。充分利用各个
运算单元,以提高性能。书中给出了k取不同值以及采用其他combine方法下性能的结果对比,具体
结论和分析见书P370-P373。*/

// 6 提高并行性2,循环展开的不同结合变换
/* 书中还给出了重新结合变换来极大提升性能。这里给出其源代码。详情可见书P373-P376。*/
/* 按照因子k = 2展开重新结合的改进版本 */
void combine_add6(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length – 1;
data_t *data = get_vec_start(v); /* v->data */
data_t acc = 0;

for (i = 0; i < limit; i+=2) /* 一次循环累加两个数 */
{
// acc = (acc + data[i]) + data[i + 1];
acc = acc + (data[i] + data[i + 1]);
}

for (; i < length; i++) /* 完成剩下的数的累加 */
{
acc = acc + data[i];
}
*dest = acc;
}
/* 可以看出,这里的combine_add6其实是combine_add4的一个改进版本。在累加的时候先求两个读取
到的元素和,再把求和结果与acc变量求和。对于整数加法来说,这样的改进对性能影响并不大,
因为整数加法的运算延迟比较小。但是对于整数乘法和浮点数加乘法来说性能确实质的改变。为什
么有这样的现象呢,主要是因为虽然每次循环要做两次加/乘运算,但是只有其中一次加/乘法会依
赖acc,这就导致从内存读取出来的两个数在相乘的时候并不会对数据数据相关链产生影响,可以
并行执行,从而使效率提升一倍。本质上依然是一种提高并行性的优化方法。 */

int main()
{
const long LEN = 1000000;
vec_ptr vp = new_vec(LEN);
for(int i=0;i<LEN;i++)
vp->data[i] = i+1;
data_t dt = 0;
clock_t start,end;
start = clock();
combine_add0(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 0 低效率版本\n",double(end-start),dt);
start = end;
combine_add1(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 1 减少循环中的函数调用1\n",double(end-start),dt);
start = end;
combine_add2(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 2 减少循环中的函数调用2\n",double(end-start),dt);
start = end;
combine_add3(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 3 消除循环中不必要的内存引用1\n",double(end-start),dt);

start = end;
combine_add4(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 4 循环展开\n",double(end-start),dt);
start = end;
combine_add5(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 5 提高并行性\n",double(end-start),dt);
start = end;
combine_add6(vp,&dt);
end = clock();
printf("时间消耗:%5.2f 结果:%d 6 提高并行性2,循环展开的不同结合变换\n",double(end-start),dt);
getchar();
return 0;
}
/* 运行结果
时间消耗:46.00 结果:1784293664 0 低效率版本
时间消耗:16.00 结果:1784293664 1 减少循环中的函数调用1
时间消耗: 0.00 结果:1784293664 2 减少循环中的函数调用2
时间消耗:16.00 结果:1784293664 3 消除循环中不必要的内存引用1
时间消耗: 0.00 结果:1784293664 4 循环展开
时间消耗: 0.00 结果:1784293664 5 提高并行性
时间消耗:15.00 结果:1784293664 6 提高并行性2,循环展开的不同结合变换
*/
// 总结
/* 这里主要是利用一个小程序总结了程序优化的一些方法,在实际的工程当中程序代码异常复杂,在
优化的过程中一定不能影响程序的正确性。应该注意一些函数是否有副作用,有没有改变一些全局
程序状态等。此外,对于复杂的工程,还需要一些测时间的工具和技术来确定应该主要优化哪些地
方的代码。 */
// https://blog.csdn.net/xiaji110901/article/details/79032674

-End-

拓展知识:

程序并行配置不正确

鼠标点击桌面左下角的开始按钮,选中运行打开, 在运行框中输入services.msc并回车回车,打开服务项。

在服务窗口界面中找到:Windows Modules Installer服务,查看该服务是否被禁用。

如果Windows Modules Installer服务被禁用,将它更改为启用状态,接着重启计算机再重新安装应用程序就可以了。

应用程序,指为完成某项或多项特定工作的计算机程序,它运行在用户模式,可以和用户进行交互,具有可视的用户界面。

应用程序通常又被分为两部分:图形用户接口(GUI)和引擎(Engine)。

它与应用软件的概念不同。应用软件指使用的目的分类,可以是单一程序或其他从属组件的集合,例如Microsoft Office、OpenOffice。

应用程序指单一可执行文件或单一程序,例如Word、Photoshop。日常中可不将两者仔细区分。一般视程序为软件的一个组成部分。

本回答被网友采纳

程序并行配置不正确

应用程序无法启动,因为应用程序的并行配置不正确解决方法:

1、重新安装之前重启电脑

出现这种情况,很可能是卸载其他程序后引起的错误。简单的解决办法是重新安装QQ、photoshop等软件。

2、选择其他的位置进行安装

重装之后还是不能解决,就考虑换个盘符进行安装。

3、用软件修复注册表

用常用的注册表修复软件修复一下电脑可将无关项、无关键值删除。

程序并行配置不正确

你好!

遇到这种问题,首先,打开电脑的控制面板,看有没有以下几个?或者少了哪几个?没有的话,百度查找,下载,安装,按顺序来呀,安装完毕,重启电脑

什么?都有了?那也可以,接着,打开你的Windows Update,更新你的系统,友情提示,看到一两百M的更新可以忽略,但是一些几十M几M甚至十几KB的,切记要更新。

然后,更新完毕,重启电脑,点击QQ,看还能正常运行不?还是不行?删除了,安装在其它盘符里试试,这样可以了么?还是不行?下载QQ 2012正式版,这样可以了吧?希望能帮的到你,祝你生活愉快,此方法亲测,完成率90%。。。

原创文章,作者:趣淘网小编,如若转载,请注明出处:http://www.3322388.com/75738.html

发表评论

邮箱地址不会被公开。 必填项已用*标注