2019年6月

win32diskimager是一个简单粗暴的SBC镜像写入工具。(废话)

在近期使用的时候,遇到了奇怪的问题:打开该软件后,程序并不按照预期出现。打开任务管理器观察,发现其在内存占用上升一段时间之后自动退出。

当时以为是软件数据出错的原因(数据异常变化,而磁盘修复并不能发现的问题我是遇到过的),就卸了重装。但是发现没有任何区别。在某度上搜索许久,未果。

然而咕果迅速地提供了答案。在做了大量的阅读理解题之后,在官方的一个差错追踪系统bugtrace system中发现了原因:当系统中存在ramdisk一类的虚拟磁盘的时候,该程序就会报错。卸载所有虚拟磁盘,再启动程序,正常运行。网址参考如下:https://bugs.launchpad.net/win32-image-writer/+bug/1672552

没错,就是这么皮

原因还是很奇葩,电脑上装的是VS2013,不支持C++11标准,而dlib要求编译器支持C++11标准。但是由于宇宙第一IDE体态太大,重新安装费时费力,且不打算花时间处理可能存在的卸载残留,便打起了GCC的主意。在网上查阅资料,发现GCC4.7之后就支持C++11标准了,于是打算使用配置的MinGW-W64 GCC 6.4.0进行配置。

首先修改setup.py: 第148-157行,替换:

        if platform.system() == "Windows":
            cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
            if sys.maxsize > 2**32:
                cmake_args += ['-A', 'x64']
            # Do a parallel build
            build_args += ['--', '/m'] 
        else:
            cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
            # Do a parallel build
            build_args += ['--', '-j'+str(num_available_cpu_cores(2))]

        cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
        # Do a parallel build
        build_args += ['--', '-j4']

将MSVC的编译选项换为GCC的,MinGW-W64默认编译64位程序。 其中最后一行的-j4指的是make的线程数目,如何自由修改想必不必多说。 由于可以随时使用源码包的源文件重新替换,可以直接修改文件而不做注释。

若需修改所使用的数学包,修改dlib/cmake_utils/find_blas.cmake: 第67-68, 74行

   pkg_check_modules(BLAS_REFERENCE openblas)
   pkg_check_modules(LAPACK_REFERENCE lapack)
   ...
   set(REQUIRES_LIBS "${REQUIRES_LIBS} openblas lapack")

这里使用的是pkg-config作为依赖管理器,在Windows中可以通过使用环境变量PKG_CONFIG_PATH来配置其搜索路径。简单地来说,就是通过使用pkg-config工具,可以快速地获得在编译时引入该库时所需要的选项。 比如OpenBLAS库,假设安装位置在X:\openblas\,库文件位于X:\openblas\lib\,则在该目录下应有一个pkgconfig文件夹,里面有openblas.pc文件,为pkg-config所需要的库描述文件,在make时自动生成。(按理说所有的.pc文件应该放在统一的目录下,方便查找和缩短查找路径) 则此时配置PKG_CONFIG_PATH为:set %PKG_CONFIG_PATH%=X:\openblas\lib\pkgconfig;%PKG_CONFIG_PATH% .pc文件为文本文件,里面的路径空格需要特殊处理,可以使用\来转义连接,也可以用直接把路径用双引号括起来。

编译时需要指定cmake参数:

setup.py build -G "MinGW Makefiles"  --set "CMAKE_C_COMPILER"="C:/Program Files/mingw-w64/bin/gcc.exe" --set "CMAKE_CXX_COMPILER"="C:/Program Files/mingw-w64/bin/g++.exe" --set "CMAKE_MAKE_PROGRAM"="C:/Program Files/mingw-w64/bin/make.exe"

分别指定使用MinGW(而不是MSVC)、C编译器、C++编译器和make命令进行编译。

编译过程中可能遇到的问题:

  1. 编译过程中可能遇到hypot命名空间未定义的问题,这是由于Python将C++的hypot重命名为_hypot导致的。 解决方案为在Python安装目录下include/pyconfig.h中引入#include <cmath>加入所缺失的定义。 必须要将该头文件作为第一个include,因为C++引入和顺序有一定的关系。

  2. 安装依赖库(如BLAS库)的时候,路径带有空格,在最后的生成pyd的时候,可能会导致路径错误。 推荐解决办法是重新安装依赖库之不含空格等特殊符号的路径下。 但是如果实在是懒,不想重新编译安装(指自己)的话,在此提供一种骚操作: cmake将链接时所使用的库作为命令行,以文本的形式存储起来。在本例中,存储位置为build\temp.win-amd64-3.6\Release\CMakeFiles\dlib_python.dir\linklibs.rsp。 思想是通过修改纠正其中生成错误的地方,达到目标效果。 -lgfortran也要手动加入,如果使用了gcc编译的BLAS库的话 想法是美好的,但是现实是残酷的:在每次运行setup.py build时,为了确保代码的完整性,会将这些中间文件重新生成一遍。 也就是说,即使修改了该文件,再重新时,该文件的内容又会被重新以原方式生成一遍。 好在Windows 10对于cmd做了一些对图形界面更加友好的修改,使得我们可以不用再去先右键再选中文字了。在经过多次试验后,发现当选中文字时,不但不会继续刷信息,而且运行的程序似乎也会暂停运行(类似断点的效果?)。 也就是说,可以通过在运行到链接.pyd库之前的每一行输出时,暂停检查linklibs.rsp文件,直到出现该消息为止。 通过这种方式可以顺利通过编译,链接完以后,就基本完成dlib的编译了。 至于升级pkg-config是否能解决问题,在下并不知道。

===================================================== 后续更新

在使用的时候,报了 系统找不到指定文件 的错误。也就是说,最终整个过程还是失败的。 猜测可能的原因是,编译时生成的是libdlib.a文件,属于linux操作系统下的静态库,而加载dlib库时,应该要动态加载dll文件,因而产生了该问题。同样,也不能排除文件格式和后缀不同所导致的问题。其原因有待后续深入探究。当然,最好的办法还是按照官方教程来,使用合适版本的VC++编译器来完成功能。

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具... ...当前其支持的语言限于Java、Groovy、Kotlin和Scala...

嗯,没错,这就是我把它归类到 编程语言/Java 而不是 软件/编译 的原因。

相比于maven,Eclipse对于gradle的可视化操作的友好程度大大下降。只提供最基础的项目创建、任务运行(修改了配置文件之后居然还不会自动更新任务)和配置文件高亮功能。甚至连依赖配置都只能自己动手修改文件完成。虽然不难,但是不直观啊,对新手入门也不友好。反正我是搜了好久教程,七拼八凑弄出来的。

简要列出三大运行项目的方法[1]

  1. 暴力寻找主类运行。由于gradle只是一个项目构建工具(和cmake有点像?),并不负责对被处理对象进行运行操作,故默认状态下我们只能得到编译好的class文件。故最原始粗暴的方法便是手动为Eclipse指定一个运行时的主类。而当Eclipse在检测到所有项目文件均已编译好时,会直接运行项目,间接达到了目的:因为依赖配置使用的是gradle,直接使用Eclipse调用javac编译的话可能会出现缺少包的错误。

  2. 加载application插件。gradle可以通过插件扩展其功能。application插件能够为gradle添加一个Application Tasks/run的任务,通过该任务可以直接运行项目。 在build.gradle文件的最外层里添加

    apply plugin 'application'
    mainClassName = 'your.main.class'

    your.main.class换成项目的入口类,会自动调用其main方法运行项目。 没有mainClassName的话,buildrun任务都会出错,因为application插件将不知道你需要将哪个包作为应用程序运行。

  3. 这个更暴力了。。编译成jar然后自己去命令行运行(捂脸)
    jar {
    manifest.attributes 'Main-Class': 'com.mycompany.Main'
    from configuration.compile.collect { zipTree it}
    }

    然后gradle/gradlew build生成jar包,然后用对应的java命令执行(通常是java -jar file.jar)即可。

添加依赖的方法:

  1. 最简单粗暴(对应maven):

    <scope> '<group>:<artifact>:<version>'
    =======================================================
    <scope> IN compile, test, testCompile, runtime, etc.

    scope与maven中的部分对应。 例:runtime 'mysql:mysql-connector-java' << 最后一个版本号可以省略,应该是默认取最新版本吧?

  2. 优雅一点:

    <scope> group: '...', name: '...', version: '...'
    =======================================================
    <scope> IN compile, test, testCompile, runtime, etc.

    例:runtime group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'

  3. 本地文件:
    <scope> fileTree(dir: 'lib', includes: ['mysql-connector-java-8.0.16.jar'])
    =======================================================
    <scope> IN compile, test, testCompile, runtime, etc.

    该写法会让gradle到项目根目录下的lib文件夹里寻找mysql-connector-java-8.0.16.jar

参考: [1] https://www.cnblogs.com/yongheng20/p/6161160.html

在做计算机组成题目的时候,遇到的神奇的这么一题:

假设某计算机按字编址,Cache 有 4 个行,Cache 和主存之间交换的块为 1 个字。若 Cache 的内容初始为空,采用 2 路组相联映射方式和 LRU 替换算法。当访问的主存地址依次为 0,4,8,2,0,6,8,6,4,8 时,命中 Cache 的次数是?

当时就有点纳闷:访问的主存地址都是偶数,若是最基本的组相联的映射关系的话,岂不是只有一半的缓存是有效的? 按照这种方法计算,最终是只有一次缓存命中的。

但是看着总感觉隐隐不对劲,再理论化的题目,也不应该出反实际情况的例子啊,哪有这种暴殄天物的操作。 再去翻翻书,书上也只对于Intel Pentium处理器内的2路组相联缓存做了一个让人捉摸不透的简要解释:

因为在两路组相联cache中,一个主存块只能调入cache的一个特定组的两块中的一块,因此只需设置一个标志位,当两块中的一块(假设为A块)被命中时,标志位置1,而另一块(假设为B块)被命中时,标志位清0。当需要替换是,只需检查此标志位的状态即可:为0替换A块,为1替换B块。Pentium CPU内的数据cache采用的是一个两路组相联结构,使用的就是这种简化的LRU替换算法。

(若是至此,或许可能存在的读者已经领悟其中的意思的话,可直接跳至文尾)

后来在网上找也没看到说有什么官方权威的解释,不过在某度文库的一个ppt里找到了这样的题解: https://wenku.baidu.com/view/e1c69c0cf78a6529647d5340.html 为了防止原链接抽抽,贴张截图吧

也就是说,根据这个题解在对存储单元在缓存中的映射中,采取了连续两个分为一组的规则的操作,可以大胆猜测:所谓的2路组相联操作,就是在缓存分组的时候每两个缓存单元一组,且在做映射的时候将连续的两个主存单元映射至同一组缓存分组。通过这个猜测,我们可以解释在该题解中,为什么4个缓存单元要画成2x2的形式,以及为什么分组是两个连续主存单元分至同一缓存块。

此时回过头去看上面的书本引用,会发现似乎清楚了许多:一组中只有两个缓存块,当有新数据进入时,替换的只能不是A块就是B块,因而只要根据LRU算法,哪个最近被使用的“得分”低,就会被替换出去,因而只需要一位二进制就可以表示LRU算法中的标志位。再说通俗一点,对于这个缓存块,我更愿意把它叫做“块中块”,因为首先主存在分组映射的时候,就已经需要按照一定的数学规律进行归类,而这个“2路”决定了其中归类时的连续存储单元数为2,而不是默认时的1。

据此,作出如下抽象公式式的结论: 假设有N个缓存单元可以分解为两个正整数m, n的乘积,定义n路组相联的映射规则如下: 将主存单元和N个缓存单元均按照每n个一组划分(即有m个缓存单元组),将所有(km+i)个主存单元组映射至第i个缓存单元组。 其中i为[0, m)区间内的整数,k为使得第(km+i)个主存单元组存在的自然数值。

但是,到目前为止,尚未见过2路以上的组相联缓存映射。 假设要实现的话,标记为可做如下设计: 1) 一组内有多少个寄存器就用多少位二进制,用0和1区分不需要和需要替换的寄存器,可作为选通信号直接输给硬件电路; 2) 使用二进制地址来表示需要替换的寄存单元编号,用译码器传递选通信号。

没错,回去点个题,这篇文章大部分基于个人分析,目前尚未找到权威书籍或资料证明其正确性。可能是因为太懒还没找到

============================================== 后续更新

回去翻书后,发现书上没有对n路组相联进行详细的叙述。实际上,这个n路为了方便寻址和提高硬件利用率,通常会设置为2的自然数幂(1, 2, 4, 8, ...)。在做题时,需要利用的就是组相联中的路数的含义,包括 缓存的划分主存的映射规则 。根据这个规则,以及部分博客所提供的定义,能够正确地完成题目这才是重点