不亦快斋 :: 计算机技术 http://icerote.net/blog/ 程序人生 LXC容器内使用sshfs http://icerote.net/blog/post/144 http://icerote.net/blog/post/144 <div style="direction:ltr"> <p>为了避免所做的设置分散化,我设置了一个lxc容器专门用于管理工作,这样避免了不同机器之间到处相互可以登录。我可以在管理机器上做好配置,然后下发到各工作机器。但是有时候又确实需要在工作机之间传递数据,scp又不支持从一个远程机器复制到另一个远程机器,这时将其中一台机器通过sshfs挂载到本地就会方便很多。</p> <p>lxc默认没有fuse设备,需要手动创建:</p> <pre><code class="bash">mknod -m 666 /dev/fuse c 10 229 </code></pre> <p>在lxc的config中也许可以指定创建,但是我觉得不应该采用bind选项,这里只是想独立地mount到管理容器中,而不想干扰host机器。</p> </div> Cppcms数据库迁移 http://icerote.net/blog/post/141 http://icerote.net/blog/post/141 <div style="direction:ltr"> <p>这个blog和wiki原来的数据库是sqlite,一直打算迁移到了postgresql上,拖到现在才搞。</p> <p>使用的迁移工具是<a href="https://github.com/dimitri/pgloader">pgloader</a>,非常方便。<a href="https://pgloader.readthedocs.io/en/latest/ref/sqlite.html">这篇</a>也可以参考,有其他数据库迁移到Postgres的指导。</p> <pre><code> pgloader SOURCE TARGET pgloader wikipp.db postgresql://user:password@host/database </code></pre> <p>值得一提的是postgresql 连接URL格式,可以参考<a href="https://stackoverflow.com/questions/3582552/postgresql-connection-url">https://stackoverflow.com/questions/3582552/postgresql-connection-url</a>。这种url格式新版本的libpq才能支持。</p> <p>另一个值得一提的是经SSL连接postgres。我之前的一个做法是让Postgres只监听loopback地址,再通过SSH隧道转发,这么做的优点是客户端可以用ssh的密钥而无需密码登录,但搞得有点复杂。今天顺带开了一下Postgres直接支持SSL连接。</p> <p>首先编辑<code>postgresql.conf</code>, 打开ssl:</p> <pre><code> ssl = on ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' </code></pre> <p><a href="https://www.postgresql.org/docs/9.1/ssl-tcp.html">文档</a> 说key文件的权限必须是600,但是我的机器上实际是640.</p> <p>编辑<code>pg_hba.conf</code>,添加:</p> <pre><code> hostssl database user address auth-method [auth-options] </code></pre> <p>并且把相应的host行删除,具体参考<a href="https://www.postgresql.org/docs/9.1/auth-pg-hba-conf.html">这里</a>。</p> <p>客户端的连接字符串也要相应修改,添加 <code>sslmode=require</code></p> <p>Wikipp的迁移很顺利,但是Cppblog创建新blog失败。查了一下,源码的bug,应该是键入错误,修复很简单。</p> <p>另一个问题是发现Markdown的支持有问题,没有支持Github风格的块代码。最后还是查了源代码搞定的。看源码还是终极的手段,相关源码其实并不复杂,比看网上文档还来得省力。 这几篇可以参考:</p> <ol> <li><a href="http://www.pell.portland.or.us/~orc/Code/discount/">Discount</a> 实际用的Markdown的库、命令行工具就是来自这个项目。名字实在无法让人联想到Markdown,以至于我一开始误以为找错了。这里面有libmarkdown2中所有flag的定义和解释,遗憾的是没有给出数值。另外,这也是一篇不错的Markdown扩展语法测参考。吐槽一下,libmarkdown2的man页面太简略了,还不如直接看头文件。</li> <li><a href="https://github.github.com/gfm/#task-list-items-extension-">GitHub Flavored Markdown Spec</a> Github的扩展语法。</li> <li><a href="https://michelf.ca/projects/php-markdown/extra/#def-list">php Markdown Extra</a> php扩展的列表语法。</li> <li><a href="http://tedwise.com/markdown/">Discount Markdown Syntax</a> 相当完整的Discount Markdown的语法。末尾的特殊字符表很有参考价值。</li> </ol> <p>于是给Blog扩展了一下Markdown的支持:</p> <pre><code>const int K_markdown_flags = 0x00000004| //MKD_NOPANTS 0x01000000| //MKD_DLEXTRA 0x02000000| //MKD_FENCEDCODE 0x08000000| //MKD_GITHUBTAGS 0x40000000| //MKD_LATEX 0; </code></pre> <p>修改后的代码:</p> <ul> <li><a href="https://github.com/wingfiring/cppcms">cppcms</a></li> <li><a href="https://github.com/wingfiring/cppdb">Cppdb</a></li> <li><a href="https://github.com/wingfiring/cppblog">Cppblog</a></li> <li><a href="https://github.com/wingfiring/wikipp">Wikipp</a></li> </ul> </div> 向量平行快速判定 http://icerote.net/blog/post/138 http://icerote.net/blog/post/138 <div style="direction:ltr"> <p>向量a・b点积若为0,则相互垂直,但是判断是否平行,则一般需要计算|a||b|,比较耗时。</p> <p>对于二维向量,可以通过判断a1b2=b1a2,但对于多维向量,要判断 a1/b1=a2/b2=a3/b3=...=an/bn, 考虑到bi可能为0,代码写起来比较麻烦。这里想到的一个办法.</p> <ol> <li>设i为0</li> <li>若ai和bi不都为0, 转4</li> <li>++i &lt;n 转2,否则返回true</li> <li>若bi为0,return all_of(bi, bn, equal_to(0))</li> <li>k = ai/bi</li> <li>return all_of([i -> n], k*bi == ai)</li> </ol> </div> 善用sort命令 http://icerote.net/blog/post/137 http://icerote.net/blog/post/137 <div style="direction:ltr"> <p>文本处理的时候难免需要排序,sort就是用来排序的工具.然而如果对sort不够熟悉,那很多时候不免觉得sort也力不从心.了解这些功能,可以让sort的能力更上一个台阶,见下表.</p> <p></p><table> <thead><tr><th>选项</th><th>长度</th><th>描述</th></tr></thead> <tbody> <tr><td>-b</td><td>--ignore-leading-blanks</td><td>默认情况下,对整行进行排序,从每行的第一个字符开始。这个选项导致 sort 程序忽略 每行开头的空格,从第一个非空白字符开始排序。</td></tr> <tr><td>-f</td><td>--ignore-case</td><td>让排序不区分大小写。</td></tr> <tr><td>-n</td><td>--numeric-sort</td><td>基于字符串的数值来排序。使用此选项允许根据数字值执行排序,而不是字母值。</td></tr> <tr><td>-r</td><td>--reverse</td><td>按相反顺序排序。结果按照降序排列,而不是升序。</td></tr> <tr><td>-k</td><td>--key=field1[,field2]</td><td>对从 field1到 field2之间的字符排序,而不是整个文本行</td></tr> <tr><td>-m</td><td>--merge</td><td>把每个参数看作是一个预先排好序的文件。把多个文件合并成一个排好序的文件,而没有执行额外的排序</td></tr> <tr><td>-o</td><td>--output=file</td><td>把排好序的输出结果发送到文件,而不是标准输出</td></tr> <tr><td>-t</td><td>--field-separator=char</td><td>定义域分隔字符。默认情况下,域由空格或制表符分隔</td></tr> </tbody></table></p> <p><p></p> <p>大部分很很好理解.<code>-n</code>可以按照实际数字排序,而没有的话就是按照字典排序.有了<code>-n</code>就可以避免11排在9前面这种尴尬了.<code>-k</code>对于多字段文本特别有用,比如让<code>ls</code>的结果按照文件大小排序,可以这么写:</p> <pre><code>ls -l /usr/bin | sort -nr -k 5 </code></pre> <p>我有时还需要按照字段的特定方式排序,例如对于<code>MM/DD/YYYY</code>格式的日期,想按照时间排序,sort也可以做到:</p> <pre><code>Ubuntu 8.10 10/30/2008 sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt </code></pre> <p>上面例子中的<code>3</code>表示第三个字段,<code>.7</code>表示字段的第7个字符开始,因为同时指定了<code>-n</code>,<code>-k 3.7n</code> 合起来就是<code>第3个字段的地7个字符开始的数值</code>.多个<code>-k</code>表示先按第一个k指定的字段排,在相同值的情况下再按后面的k指定字段排序.</p> <p>通常sort以空白作为字段的分隔符,但是也可以用-t指定其他的分隔符.</p> <p><code>-m</code>可以用来合并多个已经排好序的文件到一个文件中</p> <p>还有个<code>-u</code>参数,可以对排好序的结果做uniq,不必再调用<code>uniq</code>命令去重.</p> </div> bash 重定向技巧 http://icerote.net/blog/post/136 http://icerote.net/blog/post/136 <div style="direction:ltr"> <p>重定向的顺序安排非常重要,例如:</p> <pre><code>&gt;ls-output.txt 2&gt;&amp;1 </code></pre> <p>重定向标准错误到文件 ls-output.txt,但是如果命令顺序改为:</p> <pre><code>2&gt;&amp;1 &gt;ls-output.txt </code></pre> <p>则标准错误定向到屏幕。</p> <p>现在的 bash 版本提供了第二种方法,更精简合理的方法来执行这种联合的重定向:</p> <pre><code>ls -l /bin/usr &amp;&gt; ls-output.txt </code></pre> </div> 记一次字符串性能分析 http://icerote.net/blog/post/135 http://icerote.net/blog/post/135 <div style="direction:ltr"> <p>因为材质库增加了大量材质,不出意外地,Protein加载系统库时时间变长,也不出意外地,某产品那边表示性能很重要,必须解决.不多说了.</p> <p>虽说Protein 4.0的时候,因为使用了Xirang重写实现,性能比3.0快了1~2个数量级,但是那个结果其实并不是因为Xirang够快,而是Protein 3.0实在太慢了.</p> <p>Protein 4.0 其实也不快,以前虽然有过一些度量和分析,但是时间久远,,数据也没有存档.借这这一次,有机会再省视一下,发现大致有如下性能问题:</p> <ol> <li>内存分配次数问题</li> <li>隔离了实现的iterator</li> <li>低效的已序序列的并、交、差问题</li> <li>Xirang.type中将copy construction分解为default construction + assignment的问题</li> <li>string的效率问题</li> <li>lexical cast的问题</li> <li>二进制接口隔离导致的性能损失</li> <li>访问type member和sub object的性能损失</li> <li>deserialize 时接口调用带来的性能损失</li> </ol> <p>其中4、5和1的关系密切. 上面是做了优化后的结果了,优化之前1的情况还要糟糕得多.</p> <p>因为对string做了比较细致的测量,所以这里就做一下分析和记录.别的部分有心情再说.</p> <p>在一个典型应用的测试中,我记录了整个周期所有字符串的活动情况(注意,这不是某个时刻的系统快照,可能差异巨大):</p> <ul> <li>内容不同的字符串数量: 23916</li> <li>内存分配次数(除空串): 186959</li> <li>字符串共享计数: 1131117</li> <li>所有内容不同字符串长度合计: 1144861</li> </ul> <p>xirang中的string设计成了immutable的,带引用计数,但是没有做小字符串的优化.因此这里的一次内存分配必然对应一次构造,实际做了186959次内存分配.</p> <p>基于经验猜测,小字符串优化对性能影响是巨大的.那么在我的这个例子中,实际影响有多大呢?考虑在64位机器上实现string,那至少要能容纳一个指针,即8个字节.假设一个额外字节存储字符'\0',另一个字符用来做标记,那就是两个字符的额外开销.因此,我统计了长度分别为6,14,22,和30的情况如下表</p> <p></p><table> <thead><tr> <th>长度类别</th><th>数量</th><th>内存分配次数</th><th>共享计数</th><th>字符总长度</th> </tr></thead> <tbody> <tr><td>6</td><td>397</td><td>31784</td><td>111825</td><td>1728</td></tr> <tr><td>14</td><td>3402</td><td>110235</td><td>506131</td><td>33178</td></tr> <tr><td>22</td><td>6188</td><td>141249</td><td>819703</td><td>83360</td></tr> <tr><td>30</td><td>9963</td><td>150382</td><td>1006513</td><td>182629</td></tr> </tbody></table></p> <p> 百分比数据: </p> <table> <thead><tr> <th>长度类别</th><th>数量</th><th>内存分配次数</th><th>共享计数</th><th>字符总长度</th> </tr></thead> <tbody> <tr><td>6</td><td>1.66</td><td>17.00</td><td>9.89</td><td>0.15</td></tr> <tr><td>14</td><td>14.22</td><td>58.96</td><td>44.75</td><td>2.90</td></tr> <tr><td>22</td><td>25.87</td><td>75.55</td><td>72.47</td><td>7.28</td></tr> <tr><td>30</td><td>41.66</td><td>80.44</td><td>88.98</td><td>15.95</td></tr> </tbody></table> <p><p></p> <p>从上述数据来看,采用32字节大小的string可以削减80%以上的内存分配操作,但此时共享技术削减的次数更高,达到88%.因此,单从次数上来说,再增加string的大小不太可能会更经济了.但是这么分析并没有考虑到共享方式的计数器增减对cache的毒害效应,因为共享计数器必须是原子的.但无论如何,32字节可能是一个值得考虑的上界.另外,随着string变大,额外引进的内存开销增加也是巨大的.</p> <p>下面分析一下现有的string实现实际内存开销.一个string本身只含有一个指针,因此大小是8,数据部分包括计数器、hash值和size,在64位机器上大小是24,因此数据部分实际大小就是24 + N,考虑到对齐,数据部分大小将是32,40,48...这样的序列.</p> <p>对长度6字节以下的字符串而言,系统实际分配的内存,数据部分大小是32,加上string本体,合计40字节.计算内存大小的公式采用:</p> <p><code>本体大小*共享计数 + 数据大小*内存分配计数</code></p> <p>因此内存开销分别为:</p> <ul> <li>6字节以下: <code>8*111825 + 32*31784=1911688</code></li> <li>7~14字节: <code>8*506131 + 40*110235=8458448</code></li> <li>15~22字节: <code>8*819703 + 48*141249=13337576</code></li> <li>23~30字节: <code>8*1006513 + 56*150382=16473496</code></li> </ul> <p>如果为小字符串优化,当string大小取不同值时内存开销的变化:</p> <ul> <li>8字节: <code>8*111825=894600</code> <ul> <li>降低:<code>1911688 - 894600 = 1017088</code></li> </ul> </li> <li>16字节: <code>16*(111825 + 506131)=9887296</code> <ul> <li>降低: <code>(8458448 + 1911688) - 9887296 = 482840</code></li> </ul> </li> <li>24字节: <code>24*(111825 + 506131 + 819703)=34503816</code> <ul> <li>降低: <code>(13337576 + 8458448 + 1911688) - 34503816 = -10796104</code></li> </ul> </li> <li>32字节: <code>32*(111825 + 506131 + 819703 + 1006513)=78213504</code> <ul> <li>降低: <code>(16473496+ 13337576 + 8458448 + 1911688) - 78213504 = -38032296</code></li> </ul> </li> </ul> <p>因为数据搜集方法的原因,这里的内存增减变化只意味着内存使用足迹的变化,不等于某个时刻的系统状态.不难看出,16字节是一个转折点.从性能上来讲,考虑到cache,复制16个字节的数据很大概率上是远远快于修改引用计数器的.因此,对于Protein来说,有充分的理由实施小字符串优化,不但能够减少58.96%的内存分配操作,同时也降低内存使用,还能避免共享计数器对cache的毒害,一举三得.</p> <p>接下来从频率的角度分析.取top 1000的高频字符串:</p> <pre><code>Top N allocate share chars 100: 97675 726303 1716 200: 110745 888819 3894 300: 127047 942431 5446 400: 131816 967705 7878 600: 139471 989569 11372 800: 144086 1000744 15038 1000: 147762 1006869 18484 23916: 186959 1131117 1144861 百分比: Top N allocate share chars 100: 52.24 64.21 0.15 200: 59.23 78.58 0.34 300: 67.95 83.32 0.48 400: 70.51 85.55 0.69 600: 74.60 87.49 0.99 800: 77.07 88.47 1.31 1000: 79.03 89.02 1.61 23916: 100 100 100 </code></pre> <p>仅仅是前100条就占据了所有字符串一半以上内存分配操作和60%以上的共享计数开销,因此预置静态字符串池很可能是值得的.假设放300条数据,即便算上额外内存开销,也只需要10K左右--实际因为无需引用计数,将能降低内存占用,依赖于池的实现,这里不再详细计算了--但却能够消除60%~80%的内存分配和共享计数操作.代价是内存分配操作要换成查表操作,且查找失败的概率32.05%,这是额外惩罚.但对于共享计数操作部分,将是完全的净收益,因为不再需要修改共享计数器了.这里字符串池的查找性能将是至关重要的,完美hash表,前缀树都有可能是候选。</p> <p>字符串池、小字符串优化和共享数据,这三种措施可以同时实施,只是string的实现也需要分别处理这三种状态。这可能使实现变得有点复杂,但是对于Protein和使用的系统来说,字符串的性能是及其重要的,每一点微小的提高都是值得的.</p> </div> Linux 容器 (LXC) http://icerote.net/blog/post/9 http://icerote.net/blog/post/9 <div style="direction:ltr"> <h2>错误处理</h2> <ol> <li><blockquote><p><code>unshare: Operation not permitted</code></p></blockquote></li> </ol> <pre><code class="``"> echo 1 &gt; /sys/fs/cgroup/cpuset/cgroup.clone_children echo 1 &gt; /proc/sys/kernel/unprivileged_userns_clone </code></pre> <ol> <li><blockquote><p><code>WARN: could not reopen tty: Permission denied</code></p></blockquote></li> </ol> <p> 参考 <a href="https://gist.github.com/julianlam/4e2bd91d8dedee21ca6f">https://gist.github.com/julianlam/4e2bd91d8dedee21ca6f</a>,<a href="https://github.com/lxc/lxc/issues/181">https://github.com/lxc/lxc/issues/181</a> TL;DR 总之,这是因为先<code>su</code>到root,再<code>su -</code>到非特权用户,在此过程中,cgroup信息有丢失导致问题。</p> </div> Time Machine备份速度很慢 http://icerote.net/blog/post/134 http://icerote.net/blog/post/134 <div style="direction:ltr"> <p>Time Machine备份很慢,google之,发现下面的设置有效:</p> <pre><code>sudo sysctl debug.lowpri_throttle_enabled=0 </code></pre> <p>备份完再改回来:</p> <pre><code>sudo sysctl debug.lowpri_throttle_enabled=1 </code></pre> <p>备份过程中,可以通过 <code>fs_usage backupd</code> 查看文件访问详情。</p> </div> VC C1047 链接错误一例 http://icerote.net/blog/post/133 http://icerote.net/blog/post/133 <div style="direction:ltr"> <p>BRE team在编译一个已发布版本的源代码时遇到链接错误:</p> <pre><code class="">fatal error C1047: The object or library file 'XXX.obj' was created with an older compiler than other objects; rebuild old objects and libraries </code></pre> <p>编译器是vs 2015 RTM, 按照网上的提示,清理编译环境,重新build. 其实,仅仅就错误信息提示,就足以作出猜测,清理环境,问题就可能得到解决.可实际上问题依旧.开启link的/verbose选项,因为有大量的输出,并未得到有效信息.反复google,没有进展。可是在反复尝试过程中发现,并非所有项目都有此错误,进而发现某项目中并非所有obj都有此错误,最终在某项目中同时存在无错和有错的obj.因为其错误信息报告XXX.obj是老编译器生成的,于是尝试比较有错和无错的两个obj,发现头部并无显著不同,因为是ANONYMOUS OBJECT,所以dumpbin也看不到更多信息.link -disasm 也无法反汇编。无奈通过命令行,让link分别link有问题和没有问题的obj,并重定向输出到两个日志文件,然后人肉比较两个日志文件.</p> <p>比较过程中发现,错误信息的输出是在第一次处理到sqlite3这个静态库时报告的.sqlite3是一个外部静态库,但并不是唯一的一个.根据日志前面已经成功处理的静态库输出来看,应该接下来处sqlite3中的符号,此时意识到问题可能并不是出在XXX.obj上,而是sqlite3的库有问题,只是这里的错误信息非常具有误导性:'than other objects'只会联想到项目中的其他objects,但是它这里所指的恐怕是静态库中的objects.于是赶紧到版本库中查一下sqlite的历史,果然最近是有更新的.回滚到正确版本,问题消失。</p> <p>之所以会有这个问题,在于源代码的版本和第三方库版本管理方式有问题。源代码本来是有相关联的外部库commit id的,如果都用那个id来同步代码和第三方库,就不会有问题。但是因为最近要给源码打补丁,导致源码的change list id变新,此CL id下的第三方库也是新的了,并不不兼容。解决办法是将带补丁的源码只能关联到特定时间的第三方库上。</p> </div> 在Debian上配置Time Machine http://icerote.net/blog/post/132 http://icerote.net/blog/post/132 <div style="direction:ltr"> <p>首先创建一台lxc容器,发行版选择Debian:sid. 当然,创建lxc容器并非是必要的,但是这可以让你的宿主机器保持干净。我需要挂载宿主机上的一个目录到容器,并且想让容器自动启动,在容器的config文件中加入:</p> <pre><code>lxc.start.auto = 1 lxc.mount.entry = /path/to/host/dir mount/path none bind 0.0 </code></pre> <p>选择sid的目的是省去很多自己build软件的麻烦,直接apt安装就好了:</p> <pre><code>apt install netatalk </code></pre> <p>我在安装过程中遇到点小问题,参考<a href="/blog/post/131">lxc 中 avahi 启动失败的问题</a>.</p> <p>接下来编辑<code>/etc/netatalk/AppleVolumes.default</code>,删掉行<code>~/ “Home Directory”</code>, 加上你的备份目录:</p> <pre><code>/path/to/backup "Disk Name" options:tm </code></pre> <p>注意,配置文件中有这么一行: <code> :DEFAULT: options:upriv,usedots </code> 这个<code>upriv</code>的意思是使用unix权限,这意味着你需要建一个unix用户,供Mac上的time machine登陆用.建好用户,设好密码,还得记得给目录/path/to/backup设好权限,以允许用户读写。我就是因为忘了给写权限,折腾了半天才找到原因.</p> <p>接着,创建文件<code>/etc/avahi/services/afpd.service</code>,加入:</p> <pre><code>%h _afpovertcp._tcp 548 _device-info._tcp 0 model=Xserve </code></pre> <p>然后重启服务: <code> service netatalk restart service avahi-daemon restart </code></p> <p>参考:</p> <ul> <li><a href="https://www.leiphone.com/news/201406/linux-time-machine.html">https://www.leiphone.com/news/201406/linux-time-machine.html</a></li> <li><a href="https://samuelhewitt.com/blog/2015-09-12-debian-linux-server-mac-os-time-machine-backups-how-to">https://samuelhewitt.com/blog/2015-09-12-debian-linux-server-mac-os-time-machine-backups-how-to</a></li> </ul> </div>