C++优化方法

news2024/11/27 15:25:36

C++优化方法

文章目录

  • C++优化方法
    • 1.整数运算效率高于浮点
    • 2.除法和取余
    • 4.通过2的幂次进行除法和取余数
    • 5.使用数组下标
    • 6.全局变量->局部变量
    • 7.指针值不改变的->拷贝到局部变量
    • 8.变量类型
    • 9.局部变量
    • 10.指针
    • 11.指针链
    • 12.条件执行
    • 13.布尔表达式和范围检查
    • 14.布尔表达式和零值比较
    • 15.懒检测开发
    • 16.用switch()函数替代if…else…
    • 17.二分中断
    • 18.switch语句vs查找表
      • 19.循环终止
      • 20.合并循环
      • 21.函数循环
      • 22.循环展开
      • 23.统计非零位的数量
      • 24.尽早的断开循环
    • 25.函数设计
      • 函数调用的性能消耗
      • 减少函数参数传递消耗
      • 叶子函数
      • 内联函数
    • 26.使用查找表
    • 27.浮点运算
    • 28.其他技巧
    • Reference

代码优化就是找出程序运行缓慢或消耗内存较大的地方。

有的地方需要以空间换时间,有的地方可以以时间节约空间,需要根据不同平台,不同算法,不同场景做出不同选择,才能保证代码运行更快。

程序内部被频繁调用的方法、内部循环嵌套比较深的地方、递归层次较深、遍历大量数据、调用第三方库的地方最应该优化。

最近看到一篇挺好的优化文章,和大家一起分享。本篇文章均来自于《C/C++语言代码优化的经验与方法》见文后Reference。

1.整数运算效率高于浮点

整形的运算速度高浮点型,可以被处理器直接完成运算,不需要借助于FPU(浮点运算单元)或者浮点型运算库。

这就是很多嵌入式开发都会使用定点数替代浮点数的原因,定点数再用相关整数表示进行计算,就是为了加快运算速率。

如果确定整数非负,就使用效率更高的 unsigned int而不是int,无符号unsigned的除法使用更少的计算机指令。

所以,在一个紧密循环中,声明一个int整形变量的最好方法是:

register unsigned int variable_randy;

浮点数可以先将小数点右移转换为整数,运算完后在转换为小数。

2.除法和取余

在标准处理器中,一个32位的除法需要使用20至140次循环操作。

对于ARM处理器,有的版本需要20+4.3N次循环。

因此,可以通过乘法表达式来替代除法:

  • 比如循环里面需要一直除以一个数,可以在循环外先求出这个数的倒数,然后带入到循环内通过乘法计算
  • 如果知道被除数的和除数的符号,判断 a b > c \frac{a}{b} > c ba>c ,如果a,b为正,可以转换为 a > b ∗ c a > b * c a>bc

4.通过2的幂次进行除法和取余数

如果除法中的除数是2的幂次,编译器会使用移位操作来执行除法。

有符号signed的除法需要移位到0和负数,因此需要更多的时间执行。

5.使用数组下标

当需要根据某个数字来确定某个字符时,可能会这么写:

switch ( randy ) 
{
    case 0 :   sesame = 'Q';   
        break;
    case 1 :   sesame = 'C;   
        break;
    case 2 :   sesame = 'J';   
        break;
}

// or
    if (  randy == 0 )  
    sesame = 'Q';
else if (  randy == 1 )  
    sesame = 'C';
else  sesame = 'J';

使用数组下标获取字符数组的值就更快捷

static char *types="QCJ"; 
sesame = types[randy];

6.全局变量->局部变量

全局变量绝不会位于寄存器中,使用全局变量时便需要额外的读取和存储。

建议做法是拷贝全局变量到局部变量,这样可以存放在寄存器。这种方法仅仅适用于全局变量不会被我们调用的任意函数使用。例子如下:

int randy(void);
int sesame(void);
int g_num;
void func1(void)
{  
    g_num += randy();  
    g_num += sesame();
} 

// 转变 func1 为 func2
void func2(void)
{  
    int local_num = g_num;   // 只加载1次全局变量
    local_num += randy();  
    local_num += sesame();  
    g_num = localerrs;
}

7.指针值不改变的->拷贝到局部变量

void randy1( int *input ) {    
    for(int i=0; i<10; i++)    {          
        sesame( *input, i);    
    }
}

// randy1 转变为 randy2
void randy2( int *input ) {    
    int local_input = *input;    // 如果 *input 不需要改变,则传递给 local_input
    for(int i=0; i<10; i++)    {          
        sesame (local_input, i);    
    }
}

// 或将 randy 入参设为 const,表示指针不可更改,亦可优化
void randy1(const int *input ) {    
    for(int i=0; i<10; i++)    {          
        sesame( *input, i);    
    }
}

8.变量类型

基本类型:char、short、int、long(包括有符号signed和无符号unsigned)、float和double。

正确判断所使用的的变量需要什么类型,就赋予什么变量类型。

比如整数,就不要赋予 float 和 double类型; 即使是整形,也有 int8, int16, int 32, int64之分,根据数值范围进行有效选择。

9.局部变量

编译器需要在每次赋值char和short类型的时候会将局部变量减少到8位或者16位。

这对于有符号变量称之为有符号扩展,对于无符号变量称之为零扩展。

这些扩展可以通过寄存器左移24或者16位,然后根据有无符号标志右移相同的位数实现,这会消耗两次计算机指令操作(无符号char类型的零扩展仅需要消耗一次计算机指令)。

无论输入输出数据是8位或者16位,将它们考虑为32位是值得的,通过使用int和unsigned int类型的局部变量来避免这样的移位操作。

考虑下面3个函数:

int wordinc (int a)
{   
    return a + 1;
}
short shortinc (short a)
{    
    return a + 1;
}
char charinc (char a)
{    
    return a + 1;
}

结果均相同,第一个程序片段wordinc运行速度高于后两者。

10.指针

尽可能的使用引用的方式传递结构数据,也就是说使用指针,避免拷贝数据到栈中的过程。

如果确定不改变数据的值,可以将指针指向的内容定义为const 。例如:

void Print_data (const RandyData  *data_pointer)
{    
    ...printf contents of the data...
}

11.指针链

指针链经常被用于访问结构数据。例如,常用的代码如下:

typedef struct { int x, y, z; } Point3;
typedef struct { Point3 *pos, *direction; } Position;
 
void InitPos1(Position *p)
{
   p->pos->x = 0;
   p->pos->y = 0;
   p->pos->z = 0;
}

代码中会重复调用p->pos,可以缓存p->pos到一个局部变量,该局部变量指向p->pos:

void InitPos2(Object *p)
{
   Point3 *pos = p->pos;
   pos->x = 0;
   pos->y = 0;
   pos->z = 0;
}

另一种方法是:将Point3类型的数据直接写到在Position结构中,直接访问 x,y,z

typedef struct { int x, y, z; } Point3;
typedef struct { int* x, y, z,  int*dx, dy, dz; } Position;

同理,如果需要在循环中访问某个数组成员,可以先将其提取出来再访问

for(int i =0; i < 33; ++i) {
      auto pt3 = pts[i];
    // do sth with pt3....
}

12.条件执行

集中if else中的包含的关系运算符(<,==,>等)判断条件,可以执行更快,并且把最能够有效判断的条件放在最前面,可以加速判断。

int randy(int qa, int qb, int qc, int qd)
{
   if (qa > 0 && qb > 0 && qc < 0 && qd < 0)
      return a * b + c - d;
   return -1;
}

条件聚集到一起,编译器能够集中处理。

13.布尔表达式和范围检查

经常需要判断变量是否位于某个范围内,例如:

bool PointInRectangelArea (Point p, Rectangle *r)
{
   return (p.x >= r->xmin && p.x < r->xmax &&
                      p.y >= r->ymin && p.y < r->ymax);
}

更快的方法x>min && x<max可以转换为(unsigned)(x-min)<(max-min)

这对于min等于0时更为有益。

修改后代码如下:

bool PointInRectangelArea (Point p, Rectangle *r)
{
    return ((unsigned) (p.x - r->xmin) < r->xmax &&
   (unsigned) (p.y - r->ymin) < r->ymax);
}

这条应与14条相结合修改

14.布尔表达式和零值比较

处理器的标志位在比较指令操作后被设置。

标志位同样可以被诸如MOV、ADD、AND、MUL等基本算术和裸机指令改写。

如果数据指令设置了标志位,N和Z标志位也将与结果与0比较一样进行设置。

N标志表示结果是否是负值,Z标志表示结果是否是0。

C语言中,处理器中的N和Z标志位与下面的指令联系在一起:

  • 有符号关系运算 x<0,x>=0,x==0,x!=0
  • 无符号关系运算 x==0,x!=0(或者x>0)

C代码中每次关系运算符的调用,编译器都会发出一个比较指令。

如果操作符是上面提到的,编译器便会优化掉比较指令。例如:

int Randy(int x, int y)
{
   if (x + y < 0)
      return 1;
  else
     return 0;
}

尽可能的使用上面的判断方式,这可以在关键循环中减少比较指令的调用,进而减少代码体积并提高代码性能。

C语言没有借位和溢出位的概念,但编译器支持借位(无符号溢出),例如:

int sum(int x, int y)
{
   int res;
   res = x + y;
   if ((unsigned) res < (unsigned) x) // carry set?  //
     res++;
   return res;
}

15.懒检测开发

if(randy>13 && sesame=2)这样的语句中,确保 && 表达式的第1部分完成判断或者大部分判断都会在第1部分结束,这样第2部分便不需要执行。

16.用switch()函数替代if…else…

对于涉及if…else…else…多条件判断,例如:

if( randy == 1)
    Play1();
else if (randy == 2)
    Play2();
else if (randy == 3)
    Play3();

使用if else 时,将最可能执行最先判断,这样就能最快跳出if else 判断,不用继续判断;

使用switch可能更快:

switch( randy )
{
    case 1: Play1(); break;

    case 2: Play2(); break;

    case 3: Play3(); break;
}

Switch也是

17.二分中断

如果有如下判断,会很头大:

if(a==1) {
} else if(a==2) {
} else if(a==3) {
} else if(a==4) {
} else if(a==5) {
} else if(a==6) {
} else if(a==7) {
} else if(a==8)
    PlayRandy();
{
}

使用二分方式替代它,如下:

if(a<=4) {
    if(a==1)     {
    }  else if(a==2)  {
    }  else if(a==3)  {
    }  else if(a==4)   {

    }
}
else
{
    if(a==5)  {
    } else if(a==6)   {
    } else if(a==7)  {
    } else if(a==8)  {
    }
}

或者如下:

if(a<=4)
{
    if(a<=2)
    {
        if(a==1)
        {
            /* a is 1 */
        }
        else
        {
            /* a must be 2 */
        }
    }
    else
    {
        if(a==3)
        {
            /* a is 3 */
        }
        else
        {
            /* a must be 4 */
        }
    }
}
else
{
    if(a<=6)
    {
        if(a==5)
        {
            /* a is 5 */
        }
        else
        {
            /* a must be 6 */
        }
    }
    else
    {
        if(a==7)
        {
            /* a is 7 */
        }
        else
        {
            /* a must be 8 */
        }
    }
}

背后的逻辑就是在数组中找到目标数字,使用二分肯定更快点。

比较如下两种case语句:

在这里插入图片描述

18.switch语句vs查找表

Switch的应用场景如下:

  • 调用1到多个函数
  • 设置变量值或者返回一个值
  • 执行1到多个代码片段

如果case标签很多,在switch的前两个使用场景中,使用查找表可以更高效的完成。例如下面的两种转换字符串的方式:

char * Switch_String1(int condition) {
  switch(condition) {
     case 0: return "QQ";
     case 1: return "CC";
     case 2: return "JJ";
     case 3: return "RR";
     case 4: return "AA";
     case 5: return "NN";
     case 6: return "DD";
     case 7: return "YY";
     case 8: return "HI";
     case 9: return "LS";
     case 10: return "GE";
     case 11: return "LT";
     case 12: return "GT";
     case 13: return "LE";
     case 14: return "";
     default: return 0;
  }
}
 
const char* Switch_String2(int condition) {
  if ((unsigned)condition >= 15) {
    return 0;
  }
  return "QQ\0CC\0JJ\0RR\0AA\0NN\0DD\0YY\0HI\0LS\0GE\0LT\0GT\0LE\0\0" +
         3 * condition;
}

第1个程序需要240 bytes,而第2个仅仅需要72 bytes。

第2个程序解释下:返回一个字符指针地址,然后后面的 3 * condition 就是相对于一堆字符串的首地址的偏移位置,因为返回后读取时,读到\0结束,因此每次取到所需要的2个字符+\0字符,正好3个字符。

19.循环终止

循环终止条件不同会导致额外的负担。应该使用计数到零的循环和简单的循环终止条件

看下面计算n!的两个程序。第1个递增,第2个递减。

int fact1_randy (int n)
{
    int i, fact = 1;
    for (i = 1; i <= n; i++)
      fact *= i;
    return (fact);
}
 
int fact2_randy(int n)
{
    int i, fact = 1;
    for (i = n; i != 0; i--) // 或写成     for (i = n; i ; i--)
       fact *= i;
    return (fact);
}

fact2_randy执行效率更高,因为fact1_randy 中每次需要判断 i - n 的值,然后再和0判断,比直接和0判断多了1步。

20.合并循环

能用一个循环解决问题坚决不用二个。下面是一个例子:

图片

21.函数循环

调用函数时总是会有一定的性能消耗。

不仅程序指针需要改变,而且使用的变量需要压栈并分配新变量。

如果循环中一个函数经常被调用,那么就将循环纳入到函数中,这样可以减少重复的函数调用。代码如下:

for(i=0 ; i<100 ; i++)
{
    randy(t,i);
}

void randy(int w,d)
{
    // calculate lots of stuff.
}

应改为:

void randy(w)
{
    for(i=0 ; i<100 ; i++)
    {
        //calculate lots of stuff.
    }
}

22.循环展开

简单的循环可以展开以获取更好的性能,但需要付出代码体积增加的代价。

如果循环迭代次数只有几次,那么可以完全展开循环,以便消除循坏带来的负担。

循环展开可以带非常可观的节省性能,原因是代码不用每次循环需要检查和增加i的值。例如:

图片

编译器通常会像上面那样展开简单的,迭代次数固定的循环。但是像下面的代码:

for(i=0;i< limit;i++) { ... }

下面的代码(Example 1)明显比使用循环的方式写的更长,但却更有效率。

BLOCKSIZE的值设置为8仅仅适用于测试的目的。

在这个例子中,循环条件每8次迭代才会被检查,而不是每次都进行检查。

因此,尽可能的展开循环可以让我们获得更好的执行速度。

//Example 1
 
#include
 
#define BLOCKSIZE (8)
 
void main(void)
{
int i = 0;
int limit = 33;  /* could be anything */
int blocklimit;
 
/* The limit may not be divisible by BLOCKSIZE,
 * go as near as we can first, then tidy up.
 */
blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;
 
/* unroll the loop in blocks of 8 */
while( i < blocklimit )
{
    printf("process(%d)\n", i);
    printf("process(%d)\n", i+1);
    printf("process(%d)\n", i+2);
    printf("process(%d)\n", i+3);
    printf("process(%d)\n", i+4);
    printf("process(%d)\n", i+5);
    printf("process(%d)\n", i+6);
    printf("process(%d)\n", i+7);
 
    /* update the counter */
    i += 8;
 
} 
}

23.统计非零位的数量

通过不断的左移,提取并统计最低位

示例程序2每次判断4位,将4次移位合并成1次。

//Example - 1

int countbit1(uint n)
{
  int bits = 0;
  while (n != 0)
  {
    if (n & 1) bits++;
    n >>= 1;
   }
  return bits;
}

//Example - 2

int countbit2(uint n)
{
   int bits = 0;
   while (n != 0)
   {
      if (n & 1) bits++;
      if (n & 2) bits++;
      if (n & 4) bits++;
      if (n & 8) bits++;
      n >>= 4;
   }
   return bits;
}

24.尽早的断开循环

通常,循环并不需要全部都执行。

如果在循环中完成了目的,就应该尽可能早的断开循环。

例如:如下循环从10000个整数中查找是否存在-99。

found = FALSE;
for(i=0; i<10000; i++)
{
    if( list[i] == -99 )
    {
        found = TRUE;
        break;
    }
}
if( found ) 
    printf("Yes, there is a -99. Randy!\n");

假如待查数据位于第23个位置上,程序便会执行23次,从而节省9977次循环。

当然上述查找可以通过二分法等高效查找法进行。

25.函数设计

设计小而简单的函数是个很好的习惯。这允许寄存器可以执行一些诸如寄存器变量申请的优化,是非常高效的。

函数调用的性能消耗

函数调用对于处理器的性能消耗是很小的,只占有函数执行工作中性能消耗的一小部分。

参数传入函数变量寄存器中有一定的限制。

这些参数必须是整型兼容的(char,shorts,ints和floats都占用一个字)或者小于四个字大小(包括占用2个字的doubles和long longs)。

如果参数限制个数为4,那么第5个和之后的字就会存储在栈上。这便在调用函数是需要从栈上加载参数从而增加存储和读取的消耗。

看下面的代码:

int randy1(int a, int b, int c, int d) {
   return a + b + c + d;
}
 
int q1(void) {
   return randy1(1, 2, 3, 4);
}
 
int randy2(int a, int b, int c, int d, int e, int f) {
  return a + b + c + d + e + f;
}
 
ing q2(void) {
 return randy2(1, 2, 3, 4, 5, 6);
}

函数randy2中的第5个和第6个参数存储于栈上并在函数q2中进行加载,会多消耗2个参数的存储。

减少函数参数传递消耗

减少函数参数传递消耗的方法有:

  • 尽量保证函数使用少于四个参数。这样就不会使用栈来存储参数值。
  • 如果函数需要多于四个的参数,尽量确保使用后面参数的价值高于让其存储于栈所付出的代价。建议将参数放入一个结构体并通过指针传入函数,可以减少参数的数量并提高可读性。
  • 通过指针传递参数的引用而不是传递参数结构体本身。
  • 尽量少用占用2个字大小的参数。如long类型,对于需要浮点类型的程序,double也因为占用两个字大小而应尽量少用。
  • 避免函数参数既存在于寄存器又存在于栈中(称之为参数拆分)。现在的编译器对这种情况处理的不够高效:所有的寄存器变量也会放入到栈中。
  • 避免变参。变参函数将参数全部放入栈。

叶子函数

不调用任何函数的函数称之为叶子函数。

由于不需要执行寄存器变量的存储和读取,叶子函数在任何平台都很高效。

寄存器变量读取的性能消耗,相比于使用四五个寄存器变量的叶子函数所做的工作带来的系能消耗是非常小的。所以尽可能的将经常调用的函数写成叶子函数

函数调用的次数可以通过一些工具检查。下面是一些将一个函数编译为叶子函数的方法:

  • 避免调用其他函数:包括那些转而调用C库的函数(比如除法或者浮点数操作函数)。
  • 对于简短的函数使用__inline修饰()。

内联函数

内联函数禁用所有的编译选项。使用__inline修饰函数导致函数在调用处直接替换为函数体。这样代码调用函数更快,但增加代码的大小,特别在函数本身比较大而且经常调用的情况下。

__inline int square(int x) {
   return x * x;
}
 
#include 
 
double length(int x, int y){
    return sqrt(square(x) + square(y));
}

使用内联函数的好处如下:

  • 没有函数调用负担。函数调用处直接替换为函数体,因此没有诸如读取寄存器变量等性能消耗。
  • 更小的参数传递消耗。由于不需要拷贝变量,传递参数的消耗更小。如果参数是常量,编译器可以提供更好的优化。

内联函数的缺陷是如果调用的地方很多,代码的体积会变得很大。这主要取决于函数本身的大小和调用的次数。

仅对重要的函数使用inline是明智的。如果使用得当,内联函数甚至可以减少代码的体积:函数调用会产生一些计算机指令,但是使用内联的优化版本可能产生更少的计算机指令。

26.使用查找表

函数通常可以设计成查找表,这样可以显著提升性能。

sin、cos查找表可能更合适,虽然会牺牲一部分精度。

当使用查找表时,尽可能将相似的操作放入查找表,这样比使用多个查找表更快,更能节省存储空间。

27.浮点运算

浮点运算对于所有的处理器都很耗时。

请记住:

  • 浮点除法很慢。。通过使用常量将除法转换为乘法。常量的除法在编译期间计算。
  • 使用float代替double。Float类型的变量消耗更好的内存和寄存器,并由于精度低而更加高效。如果精度够用,尽可能使用float。
  • 避免使用先验函数。先验函数,例如sin、exp和log是通过一系列的乘法和加法实现的(使用了精度扩展)。这些操作比通常的乘法至少慢十倍。
  • 简化浮点运算表达式。编译器并不能将应用于整型操作的优化手段应用于浮点操作。例如,3*(x/3)可以优化为x,而浮点运算就会损失精度。因此,如果知道结果正确,进行必要手工浮点优化是有必要的。

最好的办法或许是使用定点算数运算。当值的范围足够小,定点算数操作比浮点运算更精确、更快速。

28.其他技巧

通常,可以使用空间换时间。比如sine和cosine查找表,或者伪随机数。

  • 尽量不在循环中使用++和–。例如:while(n–-){},这有时难于优化。
  • 减少全局变量的使用。除非必须声明为全局变量,使用static修饰变量为文件内访问。
  • 尽可能使用一个字大小的变量(int、long等),使用它们(而不是char,short,double,位域等)机器可能运行的更快。
  • 不使用递归。递归可能优雅而简单,但需要太多的函数调用。
  • 不在循环中使用sqrt开平方函数,计算平方根非常消耗性能。
  • 一维数组比多维数组更快。
  • 编译器可以在一个文件中进行优化-避免将相关的函数拆分到不同的文件中,如果将它们放在一起,编译器可以更好的处理它们(例如可以使用inline)。
  • 单精度函数比双精度更快。
  • 浮点乘法运算比浮点除法运算更快-使用val*0.5而不是val/2.0。
  • 加法操作比乘法快-使用randy+randy+randy而不是randy*3。
  • put()函数比printf()快,但不灵活。
  • 使用#define宏取代常用的小函数。
  • 二进制/未格式化的文件访问比格式化的文件访问更快,因为程序不需要在人为可读的ASCII和机器可读的二进制之间转化。如果你不需要阅读文件的内容,将它保存为二进制。
  • 如果你的库支持mallopt()函数(用于控制malloc),尽量使用它。MAXFAST的设置,对于调用很多次malloc工作的函数由很大的性能提升。如果一个结构一秒钟内需要多次创建并销毁,试着设置mallopt选项。

最后,但是是最重要的是-将编译器优化选项打开!看上去很显而易见,但却经常在产品推出时被忘记。编译器能够在更底层上对代码进行优化,并针对目标处理器执行特定的优化处理。

Reference

  • C/C++语言代码优化的经验与方法

欢迎关注公众号【三戒纪元】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/528982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【UE4 像素流 WEBUI插件】部署像素流

目录 一、单实例本地像素流送 步骤 1. 勾选插件 2. 打包工程并启动信令服务器 3. 创建快捷方式并启动游戏 二、单实例局域网像素流送 步骤 1. 编辑cirrus.js 2. 编辑快捷方式属性 3. 启动 三、集成WEBUI插件 一、单实例本地像素流送 步骤 1. 勾选插件 勾选使用“Pix…

数字化时代下,企业如何利用数字化提升企业竞争力?

在数字化时代的今天&#xff0c;企业越来越意识到数字化的重要性。数字化已经成为企业竞争的关键。在数字化时代下&#xff0c;企业的竞争力也已经从传统的硬实力向软实力转变。企业需要利用数字化技术来提高竞争优势&#xff0c;从而在激烈的竞争中脱颖而出。 目前&#xff0c…

Mysql_行锁、临键锁、间隙锁的理解

目录 行锁间隙锁临键锁总结 行锁 行锁&#xff0c;也称为记录锁。 当我们针对主键或者唯一索引加锁的时候&#xff0c;Mysql默认会对查询的这一行数据加行锁&#xff0c;避免其他事务对这一行数据进行修改。 间隙锁 间隙锁&#xff0c;顾名思义&#xff0c;就是锁定一个索引…

浅谈作为程序员如何写好文档:结构化写作

我作为从一名懵懂的实习生转变为工程师的工作经历中&#xff0c;伴随着技术经验的成长&#xff0c;也逐渐意识到了编写文档是知识和经验传递给其他人的最有效方式。通过文档&#xff0c;可以分享我的技术知识和最佳实践&#xff0c;使其他人更好地理解我的工作。在这里&#xf…

图解 SQL 执行顺序,清晰明了

这是一条标准的查询语句: 这是我们实际上SQL执行顺序&#xff1a; 我们先执行from,join来确定表之间的连接关系&#xff0c;得到初步的数据 where对数据进行普通的初步的筛选 group by 分组 各组分别执行having中的普通筛选或者聚合函数筛选。 然后把再根据我们要的数据进行…

alias设置快捷键vim使用说明(解决服务器上输入长指令太麻烦的问题)

1. vi ~/.bashrc打开 2. (watch -n 1 gpustat 查看gpu使用情况 太麻烦)输入i进行编辑&#xff0c;最后一行输入 alias watchgpuwatch -n 1 gpustat alias gpuwatch -n 1 gpustat alias torch180source activate torch180 3. 按esc&#xff0c;然后输入:wq保存退出 4. source…

多轴加工-可变轴轮廓铣_刀轴控制策略

可变轴轮廓铣_刀轴 刀轴是可变轴轮廓铣最重要的核心参数之一&#xff0c;控制好刀轴对生成的刀路质量至关重要。UG NX可变轴轮廓铣提供了非常丰富的刀轴控制方法&#xff0c;常用的包括远离/朝向直线&#xff08;点&#xff09;、相对于/垂直于驱动体、侧刃驱动体、插补等&…

在Apex中获取Site URL

Foreword 目前SF暂未提供直接有效的方法在Apex获取SiteURL&#xff0c;我们可以在Idea (Access URL for a Site or Community from Apex)页面投票&#xff0c;除了下面提供的一种hack思路&#xff0c;当然也可以通过Custom Label手动维护。 Format of Site URL Sandbox site …

如何搭建自己的写作素材库,快来学,方法高效简单

我们平时看过的书&#xff0c;做过的事&#xff0c;不及时记下来&#xff0c;很可能过几天就忘记了。由此看来&#xff0c;搭建自己的写作素材库非常有必要。尤其是写作者&#xff0c;写稿的速度取决于自己写作素材的储备量&#xff0c;你储备的素材越多&#xff0c;写作时便可…

【算法学习系列】01 - 求某个数组中的任意两个位置之间的累加和

文章目录 背景解决思路代码实现 背景 已经呆在自己的舒适圈有很长一段时间了&#xff08;公司快3年了&#xff0c;业务都熟的差不多了&#xff09;&#xff0c;决定开始改变&#xff08;任何时候都不晚&#xff09;&#xff0c;尝试学习解决一些算法题&#xff0c;给自己一些适…

自媒体可以去哪里找免费图片素材?

推荐6个超好用的图片素材网站&#xff0c;免费下载&#xff0c;还可以商用&#xff0c;建议收藏起来~ 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 菜鸟图库是一个综合性素材网站&#xff0c;站内有大量的设计、自媒体等相关素材&#xff0c;像图片素材就非常…

Linux篇2

Linux 0. 终端提示信息1. 文件目录结构1.1 文件目录 2. 文本编辑器VI/VIM2.1 VIM编辑器2.1 一般模式2.2 编辑模式2.3 命令模式 3. 网络配置3.1 VMware提供的三种网络连接模式3.2 静态配置网络IP地址3.3 配置主机名3.3.1 修改主机名3.3.2 配置主机名-IP地址映射关系&#xff1a;…

离散化详解

一.概念 把无限空间中有限的个体映射到有限的空间中去&#xff0c;以此提高算法的时空效率。通俗的说&#xff0c;离散在不改变数据相对大小的条件下&#xff0c;对数据进行相应的缩小。 二.适用范围 数组中元素值域很大&#xff0c;但个数不是很多。 比如将a[][1,3,100,2000,…

CloudCompare二次开发之如何通过PCL进行点云滤波?

文章目录 0.引言1.CloudCompare界面设计滤波(filter)按钮2.PassThrough直通滤波器3.VoxelGrid体素滤波器4.UniformSampling均匀采样5.StatisticalOutlierRemoval统计滤波器6.RadiusOutlierRemoval半径滤波器7.ConditionRemoval条件滤波器8.ProjectInliers投影滤波器9.ModelOutl…

js跨域的解决方案

一、什么是跨域&#xff1f; 指的是浏览器不能执行其他网站的脚本&#xff0c;简单来说是浏览器同源政策的限制&#xff0c;浏览器针对于ajax的限制。 同源政策 两个页面拥有相同的 协议&#xff0c;端口&#xff0c;域名 就是同源&#xff0c;如果有一个不相同就是不同源…

【SQL】作为前端,应该了解的SQL知识(第四弹)

&#x1f4d1;集合运算 集合运算就是对满足同一规则的记录进行的加减等四则运算。 &#x1f449; 对行数进行增减。 &#x1f4c3;UNION 取并集 集合运算符会去除重复的记录 SELECT product_id, product_nameFROM Product **UNION** SELECT product_id, product_nameFROM …

图神经网络:(图的分类)在MUTAG数据集上动手实现图神经网络

文章说明&#xff1a; 1)参考资料&#xff1a;PYG官方文档。超链。 2)博主水平不高&#xff0c;如有错误还望批评指正。 3)我在百度网盘上传了这篇文章的jupyter notebook。超链。提取码8848。 文章目录 MUTAG数据集说明图的小批量处理法图分类的基本流程改进算法 MUTAG数据集说…

Webpack4 应用

文章目录 Webpack4 应用处理CSS文件使用css-loader和style-loader内联CSS安装配置webpack.config.js编写源代码编译打包 使用css-loader和mini-css-extract-plugin外部链接CSS安装配置webpack.config.js编译打包 处理图片使用file-loader处理CSS图片安装file-loader配置webpack…

ChatGPT使用体验

ChatGPT使用体验 前言 介绍ChatGPT 体验ChatGPT 菜谱 编程学习 出行导航 导游攻略 中英翻译 电影推荐 文章总结 总结 前言 最近关于ChatGPT的话题已经火爆了&#xff0c;我也观察和体验了一段时间。平心而论&#xff0c;这东西真的黑科技&#xff0c;大多行业都能通…

Unity-Android 打包报错

目录 报错&#xff1a; 分析&#xff1a; 1.代码剔除等级设置 2.什么方式会被剔除 3.解决办法 报错&#xff1a; FileNotFoundException: Could not load file or assembly XXX or one of its dependencies at System.AppDomain.Load (System.String assemblyString, Sys…