元旦快乐!
发现上次写博客已经是去年8月份了,这小半年发生了很多事儿。
无论如何,希望新的一年可以多分享一些有趣的内容😊
发现上次写博客已经是去年8月份了,这小半年发生了很多事儿。
无论如何,希望新的一年可以多分享一些有趣的内容😊
问题来自今日力扣
偶然发现使用std::function定义dfs函数比原生函数或auto定义慢很多,使用斐波那契简单计算一下时间,代码放在最后。
结果如下,可以看到auto和普通定义的递归函数时间开销差不多,而function定义的递归函数慢了快5倍(当然随着计算量增大,倍数会逐渐减小,但还是很慢就是了)。
check fibo(30)
<dfs_ori > ret: 1346269 done! 6425ms
<dfs_func> ret: 1346269 done! 41189ms
<dfs_auto> ret: 1346269 done! 6492msgpt给出的解释如下,以后还是投入auto怀抱了~
在C++中,当你使用std::function来包装一个lambda表达式或函数时,可能会发生拷贝构造的情况。这是因为std::function是一个类型安全的函数包装器,可以包含任何可调用对象(函数指针、成员函数指针、函数对象等),因此在构造std::function对象时,会涉及到对象的拷贝构造或移动构造。
在你的情况中,当使用function<int(int,int,int)> dfs = [&](int a, int b, int c)定义dfs时,lambda表达式会被复制到dfs对象中,这可能导致拷贝构造的开销。
而当你使用auto dfs = [&](auto&& dfs, int a, int b, int c)时,使用auto关键字推断函数对象类型,不会引起多余的拷贝构造。这是因为auto将推断出lambda表达式的真实类型,而不是通过std::function进行类型擦除和包装。
因此,如果遇到性能问题,尤其是在递归调用时,使用auto推断类型可能会更有效,因为它可以避免不必要的拷贝构造,提高性能。#include <bits/stdc++.h>
#include <functional>
#include <chrono>
using namespace std;
using namespace chrono;
int dfs_ori(int n){
if(n <= 1)
return 1;
return dfs_ori(n-1) + dfs_ori(n-2);
}
int main(){
cout << "check fibo(30)" << endl;
int n = 30;
// dfs ori
auto start = steady_clock::now();
int ret = dfs_ori(n);
cout << "<dfs_ori > ret: " << ret << " done! ";
auto end = steady_clock::now();
auto last = duration_cast<microseconds>(end - start);
cout << last.count() << "ms" << endl;
// dfs function
function<int(int)> dfs_func = [&](int n){
if(n <= 1)
return 1;
return dfs_func(n-1) + dfs_func(n-2);
};
start = steady_clock::now();
ret = dfs_func(n);
cout << "<dfs_func> ret: " << ret << " done! ";
end = steady_clock::now();
last = duration_cast<microseconds>(end - start);
cout << last.count() << "ms" << endl;
// dfs auto
auto dfs_auto = [&](auto&& dfs_auto, int n) -> int {
if(n <= 1)
return 1;
return dfs_auto(dfs_auto, n-1) + dfs_auto(dfs_auto, n-2);
};
start = steady_clock::now();
ret = dfs_auto(dfs_auto, n);
cout << "<dfs_auto> ret: " << ret << " done! ";
end = steady_clock::now();
last = duration_cast<microseconds>(end - start);
cout << last.count() << "ms";
return 0;
} 关于CIDEr的介绍可以看这里,个人感觉讲的比较清楚
唯一有个问题是计算TF的时候,提供的公式是term_freq/sum(all term_freqs),但是已知的代码库(如pyciderevalcap)里只有term_freq项,提了一个issue在这里 https://github.com/tylin/coco-caption/issues/66
Python中创建对象时,会涉及__init__和__new__两个函数,个人感觉前者较为常见。
创建一个类对象首先会调用该类的\_\_new\_\_方法(接下来省略下划线),new方法本身接受一个类参数cls,一般就是自身,随后会通过super函数得到该类的父类,并调用父类的new方法进行对象的创建(因为所有类都基于object,所以最终都会调用object的new方法)
super函数(实际上是类,这里为了简明,使用相同功能的函数替代)的输入为类cls,和某个实例obj,super做的事情就是找到obj的父类并返回,这里我们使用python的mro函数得到obj对象对应类的父类列表。mro这个类方法是python解决多重继承问题的方案,简单理解就是mro会返回调用类的父类链,列表靠前的元素是靠后元素的子类。那么super的做法就是得到obj对象类的mro列表,取出mro列表中cls的后一个元素,也就是cls的最近父类。
def super(cls, obj):
mro = obj.__class__.mro()
return mro[mro.index(cls) + 1]当然,super函数的输入也不一定是obj,比如super(cls, obj_cls),因为我们想在new函数中调用父类的方法,此时对象还没创建,所以只能传obj对应的类。对应的操作无非就是通过obj_cls.mro()来得到父类列表了。
注1:super函数既适用于实例也适用于类本身,例如super(cls, cls)返回的是cls的父类,而super(cls, cls_obj)返回的是一个cls的父类实例;两者的区别在于调用init或其他实例方法时是否要传入self,这个细节可以看接下来的代码注释
类实例创建完成后,就会调用该实例的init方法进行初始化,init函数至少有一个参数self,这就指向之前new方法创建出来的对象。
以一段代码为例,首先定义class B,父类是object(缺省也可以,从python3开始默认的基类就是object);定义class A,父类是B;A和B都各自重写了new/init两个函数。接下来创建A对象,结果见注释。
class B(object):
def __new__(cls):
print("B.__new__ called")
return super(B, cls).__new__(cls)
def __init__(self):
pass
class A(B):
def __new__(cls):
print("A.__new__ called")
print(cls.mro())
return super(A, cls).__new__(cls)
def __init__(self):
print("A.__init__ called")
super(A, self).__init__() # 注1:如果super传入的不是实例self,而是类对象本身,那么这行代码应该写成 super(A, A).__init__(self)
A()
# output
# A.__new__ called
# [<class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# B.__new__ called
# A.__init__ called结果解释:
A.__new__ called随后super(A, cls)返回了cls的父类,实际上这里的cls也就是A,写成super(A, A)或者super(cls, cls)都可以
super(A, cls).__new__(cls),即B.__new__(cls),因此输出B.__new__ calledsuper(B, cls).__new__(cls)等同于object.__new__(cls),也就是一切对象创建的起点,然后嵌套(嵌套这里表达欠妥,可见注3)返回,此时我们得到一个创建出来的A对象aa.__init__(a),从而输出A.__init__ called注2:以上代码中很多可以省略,例如__init__(self)中的self,super(A, cls)中的A和cls,这里写出来是为了更好的解释每个函数的作用
在菱形继承中,init调用顺序更加复杂,但本质上还是和super函数涉及到的mro列表有关,可见以下代码
class A(object):
def __init__(self):
print("A init")
super().__init__()
class B(A):
def __init__(self):
print("B init")
print("B's mro: " + str(B.__mro__))
print("self's mro: " + str(type(self).__mro__))
super().__init__() # 根据self实例对应类的mro列表调用init函数,如果这里使用super(B, B).__init__(),就会跳过C的实例化
class C(A):
def __init__(self):
print("C init")
super().__init__()
class D(B, C):
def __init__(self):
print("D init")
super().__init__()
d = D()
# output
# D init
# B init
# B's mro:(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
# self's mro:(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
# C init
# A init撒花,mro相关内容可能之后会更吧(
Typecho加个email提醒咋这么费劲呢....
腾讯云的主机升级php挺费劲的,试过remi源直接安装php8,但是typecho识别出来的还是老版本.
几个插件都不维护了,找到一个比较新的这里, 但是存在数据库相关的问题
虽然复盘完也就两个小问题,但是当时真的搞半天,原因可能有
发送邮件我选择了同步发送,所以评论响应有点慢,请多见谅