当前位置: 首页 > wzjs >正文

做短视频的网站收益qq群推广引流免费网站

做短视频的网站收益,qq群推广引流免费网站,wordpress视频代码html5,开发公司总经理岗位职责因为篇幅原因分成了两篇文章,这篇是上一篇文章相关的附加内容,算是补充材料,作为知识点了解一下。 1、C语言中的回调函数 看一下C语言中回调函数的参数传递的情况,因为C语言中没有模板,而且函数也没有类型&#xff0c…

因为篇幅原因分成了两篇文章,这篇是上一篇文章相关的附加内容,算是补充材料,作为知识点了解一下。

1、C语言中的回调函数
看一下C语言中回调函数的参数传递的情况,因为C语言中没有模板,而且函数也没有类型,因此不能通过定义函数类型来直接传递函数。所以在C语言中,传递回调函数时没有别的选择,只能以函数指针的形式来传参,在调用回调函数时也无法进一步优化。比如:

// 在一个C源文件中定义,作为公共实现
void invoke_in_c(int x, void(*pf)(int)) {pf(x+10);
}// 在另一个C源文件中
static void foo(int x) {printf("%d\n", x);
}extern void invoke_in_c(int x, void(*pf)(int));
void test1() {invoke_in_c(42, foo);
}

使用-O2优化选项,gcc生成的相关汇编指令代码如下:

invoke_in_c:add     edi, 10jmp     rsi

因为编译器在编译invoke_in_c()时不知道函数指针参数会指向哪一个函数,因此无法对回调函数使用内联优化,也就是在调用函数指令中:jmp rsi,寄存器rsi指向一个函数入口,但是指向哪一个函数是不知道的,只有在运行时才知道,属于动态绑定。

.LC0:.string "%d\n"
foo(int):mov     esi, edixor     eax, eaxmov     edi, OFFSET FLAT:.LC0jmp     printf
test1():mov     esi, OFFSET FLAT:foo(int)mov     edi, 42jmp     invoke_in_c(int, void (*)(int))

因为调用invoke_in_c()函数时需要传入一个函数指针,尽管函数foo()非常短小,而且使用static修饰了,因为无法内联优化它,只能生成foo()的汇编代码,并且把它的入口地址作为参数传递给invoke_in_c()函数。

看一下使用CPP的模板函数时,和前面使用C语言回调实现的功能完全一样:

static void foo(int x) {printf("%d\n", x);
}template<typename T>
void invoke_in_cpp(int x, T t) {t(x+10);
}void test2() {invoke_in_cpp(42, foo);
}

使用相同的编译选项,生成的汇编代码:

.LC0:.string "%d\n"
test2():mov     esi, 52mov     edi, OFFSET FLAT:.LC0xor     eax, eaxjmp     printf

foo()函数直接被内联优化了,它的函数体直接在test2()函数中展开,成为test()函数体的一部分,节省了函数调用跳转以及栈帧的创建和销毁等操作,因为是static修饰的,只会在当前文件内使用,所以编译器也没有为它生成汇编代码。

所以,这就是为什么在C++中的sort,要比C中的qsort性能要好了,因为C++中的比较函数(一般很短小,大概率被内联优化),可以在sort()内被编译器内联优化,而C语言只能使用函数指针,它抑制了编译器的内联优化。

2、回调函数对象的参数位置
在上一篇处列出了一些的标准库中的算法:

template< class T, class Compare >
const T& max( const T& a, const T& b, Compare comp );template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );template< class InputIt, class T, class BinaryOp >
T accumulate( InputIt first, InputIt last, T init, BinaryOp op );

可以看出,算法函数中的那些回调函数参数,它们全部都放在了参数列表的最后一个位置,这样设计应该也是有意为之的。

我们分析一下这样做的目的,下面模仿了std::max()算法,定义了两个功能相同的算法函数模板,但回调函数参数在参数列表中处在不同位置:

// cppreference中推荐的实现:
template<class T, class Compare> 
const T& mmax(const T& a, const T& b, Compare comp) {return (comp(a, b)) ? b : a;
}//Compare comp放在第一个参数位置:
template<class T, class Compare> 
const T& nmax(Compare comp, const T& a, const T& b) {return (comp(a, b)) ? b : a;
}// 一个比较函数
__attribute__((noinline))
bool comp(const int &a, const int &b) {return a < b;
}

编译器为两种传参形式的算法函数,生成的汇编指令代码如下:

int const& mmax<int, bool (*)(int const&, int const&)>(int const&, int const&, bool (*)(int const&, int const&)):push    rbpmov     rbp, rsipush    rbxmov     rbx, rdisub     rsp, 8call    rdxtest    al, alcmovne  rbx, rbpadd     rsp, 8mov     rax, rbxpop     rbxpop     rbpret
int const& nmax<int, bool (*)(int const&, int const&)>(bool (*)(int const&, int const&), int const&, int const&):push    rbpmov     rax, rdimov     rbp, rdxpush    rbxmov     rbx, rsimov     rsi, rdx ; 调整参数位置mov     rdi, rbx ; 调整参数位置sub     rsp, 8call    raxtest    al, alcmovne  rbx, rbpadd     rsp, 8mov     rax, rbxpop     rbxpop     rbpret

在x86环境中的函数调用约定,第1、2、3参数位置的参数分别使用寄存器rdi、rsi和rdx来传递,为了符合调用约定,nmax编译后完的汇编代码中,会比mmax编译后的汇编代码中多两条指令,用来调整参数的传参寄存器:

    mov     rsi, rdxmov     rdi, rbx

mmax在传参调用回调函数comp时,它的的第1个参数a也是comp回调函数的第1个参数,第2个参数b也是comp的第2个参数,mmax和comp的参数位置是一一对应的,不需要做调整;而nmax的第1个参数是回调函数comp的函数指针,第2个参数a是comp的第1个参数,第3个参数b是comp的第2个参数,因此,在nmax中调用comp时,需要把nmax的第2个参数rsi和第3个参数rdx分别赋值到rdi和rsi寄存器中,以符合调用约定,同时第1个参数rdi存放的是comp的函数指针,还得要把这个rdi寄存器腾让出来,以存放comp的第1个参数,因此会有更多的指令来调整这些参数的位置存放。

当然,这些细微的差别在内联优化编译时可能都不存在了,也可能算法函数的实现非常复杂,这些调整参数位置的指令开销比起总体开销来也微不足道,不过这些算法函数毕竟是标准库的公共函数,是构成上层应用程序的基础,每一处细节上的性能开销都是应该考虑的,性能提升再微不足道也是值得的。

因此,我们在定义算法函数模板时,如果算法函数有多个参数,一般要把回调函数放在参数列表的最后一个位置,而且如果回调函数要用到前面这些参数的话,这些参数也应该按照回调函数的参数列表的顺序来放置参数,这样在算法中调用回调函数时,有可能会避免一些寄存器分配的微调整,避免一些没有必要的性能损失。

3、函数对象的static成员函数
在上文空函数对象的测试用例中,如果把编译器换成CLANG编译器,即使使用-O3最高的优化选项,编译后的调用成员函数的汇编代码,因为没有像GCC编译器的ipa-sra优化选项,也不会把this指针这个没有用到的参数去掉。
为了阅读方便,把测试空函数对象的代码列在下面:

template<typename T>
__attribute__((noinline)) // 测试时如果允许内联,注释该行
void invoke_by_value(int x, T t) {t(x);
}template<typename T>
__attribute__((noinline)) // 测试时如果允许内联,注释该行
void invoke_by_forward(int x, T &&t) {t(x);
}struct empty_obj { // 空函数对象__attribute__((noinline))void operator()(int x) {printf("%d\n", x);}
};void test_by_value() {invoke_by_value(42, empty_obj{});
}void test_by_forward() {invoke_by_forward(42, empty_obj{});
}

下面使用CLang -O3编译后的汇编指令:

test_by_value():mov     edi, 42jmp     void invoke_by_value<empty_obj>(int, empty_obj)void invoke_by_value<empty_obj>(int, empty_obj):push    raxmov     esi, edilea     rdi, [rsp + 7] ; 传递函数对象this指针call    empty_obj::operator()(int)pop     raxrettest_by_forward():push    raxlea     rsi, [rsp + 7] ; 传递函数对象this指针mov     edi, 42call    void invoke_by_forward<empty_obj>(int, empty_obj&&)pop     raxretvoid invoke_by_forward<empty_obj>(int, empty_obj&&):mov     eax, edimov     rdi, rsimov     esi, eaxjmp     empty_obj::operator()(int)

上面invoke_by_value()和test_by_forward()函数中的指令lea rsi, [rsp + 7]就是调用函数对象的成员函数时在传递它的this指针,尽管没有什么用。因为要使用rdi寄存器来传递this指针,需要对rdi的原值进行缓存,不得不花费额外的指令。

因为要保证C++的语义(成员函数的第一个参数是隐藏的this指针参数),必须要这么做,除非像GCC那样可以进行“过程间分析(IPA)”,知道调用的函数有一个参数没有使用,把它去掉,即ipa-sra优化,这些优化都是编译器自己的优化选项,不是标准规定的,不是所有编译器都支持。不过,在C++23中提出了函数对象类中的static成员函数特性,可以利用这个特性,对空函数对象进行优化,以去掉用不到的this指针。例如,把类empty_obj 中的operator()操作符重载函数加上static修饰:

struct empty_obj { // 空函数对象__attribute__((noinline))static void operator()(int x) {printf("%d\n", x);}
};

使用CLANG -std=c++23 -O2编译后:

test_by_value():mov     edi, 42jmp     void invoke_by_value<empty_obj>(int, empty_obj)void invoke_by_value<empty_obj>(int, empty_obj):jmp     empty_obj::operator()(int)test_by_forward():push    raxlea     rsi, [rsp + 7]mov     edi, 42call    void invoke_by_forward<empty_obj>(int, empty_obj&&)pop     raxretvoid invoke_by_forward<empty_obj>(int, empty_obj&&):jmp     empty_obj::operator()(int)

代码简单多了,按照C++语义,static函数不需要传递this指针,因此在调用static成员函数时,也就不需要传递this参数了。但是,因为在test_by_forward()中,回调函数的参数类型是引用,还得要传递一个empty_obj对象引用进去,此时需要把它的对象指针(即this指针)传递进去,即lea rsi, [rsp + 7]指令,这是C++的引用语义规定,尽管毫无用处。这样,传递函数对象引用类型的test_by_forward(),反而比传递值类型的test_by_value()性能要稍差一些。

4、没有捕捉变量的lambda表达式
对于没有捕捉外部变量的lambda表达式,我们知道它等同于空函数对象,如:

void test_by_value() {invoke_by_value(42, [](int x) {printf("%d\n", x);});
}void test_by_forward() {invoke_by_forward(42, [](int x) {printf("%d\n", x);});
}

在CLNAG -std=c++11 -O2编译时生成的汇编代码:

test_by_value():jmp     void invoke_by_value<test_by_value()::$_0>(int, test_by_value()::$_0)void invoke_by_value<test_by_value()::$_0>(int, test_by_value()::$_0):jmp     test_by_value()::$_0::operator()(int) consttest_by_forward():jmp     void invoke_by_forward<test_by_forward()::$_0>(int, test_by_forward()::$_0&&)void invoke_by_forward<test_by_forward()::$_0>(int, test_by_forward()::$_0&&):jmp     test_by_forward()::$_0::operator()(int) consttest_by_value()::$_0::operator()(int) const:lea     rdi, [rip + .L.str]mov     esi, 42xor     eax, eaxjmp     printf@PLTtest_by_forward()::$_0::operator()(int) const:lea     rdi, [rip + .L.str]mov     esi, 42xor     eax, eaxjmp     printf@PLT

很显然,test_by_value()和test_by_forward()都非常简单,而调用lambda对象的operator()成员函数时,没有使用任何参数,在GCC编译器中也是如此。可见对于没有捕捉外部变量的lambda表达式,编译器会对它们进行特殊对待,毕竟它们都是就地声明就地使用的匿名对象,也不可能在别的地方使用,编译器可以充分的进行优化,而不会影响到别的地方(空函数对象不同,它的operator()有可能在别的地方调用,不可能优化的这么彻底)。把上面的汇编代码反汇编成等效的C++代码的话,如下:

void test_by_value() {invoke_by_value<test_by_value()::$_0>();
}void invoke_by_value<test_by_value()::$_0>() {test_by_value()::$_0::operator()(int) const
}void test_by_value()::$_0::operator()(int) const {printf("%d\n", 42);
}void test_by_forward() {invoke_by_forward<test_by_forward()::$_0>();
}void invoke_by_forward<test_by_forward()::$_0>() {test_by_forward()::$_0::operator()(int) const;
}void test_by_forward()::$_0::operator()(int) const {printf("%d\n", 42);
}

由此可见,编译器编译完之后的核心代码是printf(“%d\n”, 42);,中间没有任何对象的创建,没有任何参数(包括this指针)的传递,直接把常量42传播到lambda表达式的operator()(int)函数中。由此可见,即使没有C++23中函数对象的static成员函数特性,使用lambda表达式也能达到优化掉this指针的目的,优先使用lambda表达式而不是定义空函数对象类,不但编写代码方便,而且性能还更好。

综上。

http://www.dtcms.com/wzjs/259853.html

相关文章:

  • seo岗位是什么意思seo赚钱方法大揭秘
  • dreamweaver做动态网站网络销售怎么干
  • 京东网站建设现状分析搜索推广出价多少合适
  • 公司网站忘了怎么做微博指数查询入口
  • 做装修网站郑州seo优化
  • wordpress 帮助插件seo快速排名
  • 网站建设的技术有哪些方面成都seo培
  • discuz怎么做网站网上国网app推广方案
  • 重庆网领网站建设公司网站备案流程
  • 凡客建站网长春百度seo公司
  • 昆明网站制作网页网络营销所学课程
  • 广州站在哪个区教师遭网课入侵直播录屏曝光广场舞
  • wordpress archive模板长沙网站seo分析
  • wordpress推介联盟百度seo查询系统
  • 网站被iframe站长工具的使用seo综合查询运营
  • 网站建设费用能否计入广告费sem竞价教程
  • 苏州做网站0512jinyan免费b站网站推广
  • 简便网站建设大学生网页制作成品模板
  • 义乌商城集团的网站建设网络营销章节测试答案
  • 网站源码是用什么做的温州网站建设开发
  • 湖北网站建设优化app001推广平台
  • 淘宝客必须建网站吗cps广告联盟平台
  • 做网站的接口是意思百度指数指的是什么
  • jsp动态网站开发技术与实践教程怎么做好网站营销推广
  • 12306网站如何做解绑360优化大师官方版
  • 做网站需要服务器seo数据是什么
  • 巴中建设银行网站免费的seo
  • 公司的网站难不难做手机优化大师怎么退款
  • 各学院二级网站建设通报58同城推广效果怎么样
  • 网页显示站点不安全seo查询爱站网