JincResize 代码重构(3)

JincResize avs 版迁移工作告一段落了,速度比之前快了 5-6 倍,在这个过程中学到了 C/C++ 结构、C++ 类和对象(特别是通过调用构造函数、公有函数实现抽象)、C++ 内存对齐与回收的知识,对堆栈有了初步的概念,同时对程序整体设计有了大致的概念。

当修改好 readme,发布了 JincResize-r7-RC1,再来回看 commit 历史,有些感动。在这一周的时间中,有些印象很深的改动,都忘记了是早期的几天就完成的;而同时在最初的几天,以现在的视角看,更多的是四处尝试,像在黑暗中一样,但最终是看到了光亮。

如果说早期的 coding 是崎岖但知道方向;那么中期之后,在我得到一份能看到结果、但带有问题的代码时,再往前走,就有些戏剧性了。虽然通过 Debug 找到一些改正的线索,但还不够,真要靠一些运气才走下去的。当时主要是为了在实际代码中应用一下类,其实不用类完全也可以实现当时预想的目的。在这个过程中学到基础知识且不提,在把计算 Lut(Look up table)的过程放到Lut类后,我突然意识到,把整个计算好的 Lut 表传给函数,(占用了过多内存),可能是导致编译好dll调用一次就崩溃的原因。这时我才明白 avs 版代码为什么要封装一个函数,Lut 表中的数据随用随调。虽然是仿写,但我自己能独立想到原因,这也是值得欣喜的。之后又经历了一两天的折腾,才明白怎么去正确地初始化变量。终于不崩溃了。

虽然还有一个 bug 未解决,但这两三天也为此花了不少时间,该放下就放下,先告一段落吧,这一周的工作已经收获很多了。

要学会发现和留住成就感,这是增加自信的一种方式。

内存管理

_aligned_malloc()_aligned_free()是微软提出的动态申请、释放内存对齐函数。

1
2
void* _aligned_malloc(size_t size, size_t alignment);
void* _aligned_free(void* memblock);

Examples:

1
2
_aligned_malloc(array_size * sizeof(float), 64);
_aligned_free(array);

Windows 下的 gcc 编译器也可以接受上述函数,但 Linux 下的 gcc 编译器就不行了。

Linux 下的通用替代方法且不提,在 VapourSynth 下有专门的替代函数vs_aligned_malloc()vs_aligned_free(),用法和前面的原始函数一样(虽然就是给 Linux 下的相应函数起个别名,但毕竟方便很多)。

从结构到类

这次迁移了 AviSynth 版 JincResize 中的 EWA(椭圆加权平均)重采样方法,关于算法本身的疑问先放一下(因为我还是觉得,AviSynth 版用的重采样也是通过圆划定范围、进行计算,而非椭圆——虽然直观时上觉得,干脆用椭圆的长轴长为半径画圆不是更精确吗?或许椭圆本身能减少不太必要的计算?),在实现算法时,设计了两个类,一个(辅助的)类用来存放重采样/反向映射用的圆/椭圆半径、指针移动相关的数据,另一个类用来存放传递过来的核心Lut表及其他辅助数据(包括前一个类)。

以上这些都是常规设计,但计算时的函数调用,让我感觉把结构改成类,或许看上去更 C++ 一点。

如果就是单独存一些数据,那似乎是没必要把结构写成类,但类似构造函数的初始化、计算,类似析构函数的内存释放都出来了,而且就单独写一个函数放在头文件里,可能写成类和成员函数看上去更舒服一点。

带着这种想法,我打算先把主 cpp 中计算 Lut 表的过程单独拿出来写成类(至于上面提到的 EWA 重采样过程,既然结构已经完善,先这样放着吧)。具体的过程,写几个私有变量、构造函数、Lut 计算函数、空的析构函数,再写一个销毁数组释放内存的函数,大概就 OK 了。

调用,用 new 方法新建一个 Lut 对象(实际操作为创建一个数组),将Lut计算函数作用于该对象(由于数组用new方法创建,所以用指针运算符->访问成员函数),将 lut 数组定义为 Lut 类的公有变量,在主cpp里调用 lut 数组,传递给后续函数(commit 8fbcfdc)。

不要一次都塞进来

在当时,代码存在一个问题,虽然能正常运行,但只能运行一次,每次关闭 vsedit 的预览窗口,再打开,就会崩溃。

我想到,现在把计算 Lut 表的过程封装/抽象成类,但本质上和之前没有区别,还是把整个 lut 数组传递给函数,这可能导致内存泄漏,可能是导致崩溃的原因。

同时,我发现我在写构造函数、创建lut数组时,没有确定数组长度,像下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class
{
private:
int lut_size;
double radius;
double blur;
};

Lut::Lut()
{
#if defined(_MSC_VER)
lut = new double[lut_size];
#else
lut = (double*)_mm_malloc(sizeof(double) * lut_size, 64);
#endif
}

于是从两个方向入手解决。

  • 增加获取 Lut 数组的函数,这样改后应该是一次传递一个,而不是把每次都把整个数组传递过去(commit 70100f

    1
    2
    3
    4
    float Lut::GetFactor(int index)
    {
    return (float)lut[index];
    }
  • lut_size手动设定初始值(commit 5fcf58),虽然代码不好看,但毕竟解决问题了

    1
    2
    3
    4
    5
    class
    {
    private:
    int lut_size = 1024;
    };

Debug

基于 vsedit 自带的信息

最开始写好初版代码,编译好后,vsedit 居然打不开了。于是先打开 vsedit 再把 dll 丢进去,终于看到报错信息,原来是我把 python 接口的float写成了foat…orz…在编译时肯定看不出来问题,在调用时肯定出问题…

VS Debug

调试方法

在“调试”-“命令”里把 vsedit 的路径写进去,就可以用 VS 调试了。

一开始我傻傻地把 gcc 编译好 dll 丢进去,让 VS 调试…呃,肯定是加载不了啊。

崩溃问题

前面提到的lut_size没有初始化,就是借助 VS Debug 发现的。

文字的 Bug

虽然用断点查看具体计算过程有些有趣,但对解决文字 Bug 没有实质性的帮助。这个问题先放一放吧。