本开发周期实现并完善了Sudoku类和SudokuGenerator类。
0. GitHub项目地址
本项目对应的GitHub代码仓库点此进入
4. 软件实现
在软件实现阶段,我们将实现第三章所述程序设计。
4.1 实现Sudoku类
根据前述软件设计,在Sudoku类中,有如下成员:
- 变量
- int[][] data 保存当前数独的局面。
- 方法
- Sudoku(const int[][] = NULL) 构造函数,初始化数独
- Print():void 将数据输出至控制台
- PrintToFile(const String& fileName): void 向特定文件输出数独
- operator=(const Soduku&):Soduku 重载赋值运算符
- Sudoku(const Soduku&):Soduku 重载拷贝构造函数
- ~Sudoku() 重载析构函数
Sudoku类的实现已签入github。
4.2 实现SudokuGenerator类
根据前述软件设计,在SudokuGenerator类中,有如下成员:
- 变量
- String outputFile 指示生成的数独应当输出到哪个文件
- int sodukuNum 指示生成的数独个数
- Sudoku* Sudokus 存放生成的数独
- 方法
- SudokuGenerator(String outputFile, int sodukuNum) 构造函数
- GenerateSudoku():void 生成数独
- ValidateInput(const String[] parameters) :bool 验证输入是否有效
- GenerateBaseSoduku(int num):void 生成基础数独,基础数独的定义见开发日志3
- ExpandSoduku():void
Sudoku类的实现已签入github。
6. 性能改进
6.1 SudokuGenerator类
主程序如下图。
运行VS2019 性能分析工具,结果如下图。
可见fclose函数是所有函数中占用时间最长的,其调用次数也远远超过其他热点函数。由于这是一个系统函数,我们无法进行优化,因此我们试图减少它的调用次数。分析程序可知,由于模块间及模块内部传递文件信息时使用的是文件名,因此对每一个基础数独,我们都需要打开、关闭一次文件指针。因此,我们改用传递文件指针。这样就可以避免频繁地打开关闭文件。
据此对Sudoku类和SudokuGenerator类进行重构。由于用户仅可见SudokuGenerator类,我们仅保留SudokuGenerator的构造函数使用字符串传递文件名,其余的接口全部改用文件指针传递文件信息。
重构后,再次运行性能分析工具,结果如下。
可见运行时间大大降低,生成100万组数据用时降低91.88%。查看调用树如下。
发现用数最多的函数为Sudoku类的构造函数中的new运算。分析后发现,这里并无动态分配内存的必要,因此改用静态分配内存。同时,并不需要为每一个输出的数独一个Sudoku对象,仅需为基本数独产生即可。进行修改,再次运行性能分析工具。结果如下。
可见此时new运算已经不再是热点,生成100万组数据用时4.581s,且主要花费在磁盘IO上。本次改进是的性能再次提升33.906%,相比最初版本运行时间降低94.631%。