<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>徑庭</title>
  
  <subtitle>吾闻言於接舆，猶河漢而無極也</subtitle>
  <link href="https://jhuang.me/atom.xml" rel="self"/>
  
  <link href="https://jhuang.me/"/>
  <updated>2021-01-01T03:34:22.091Z</updated>
  <id>https://jhuang.me/</id>
  
  <author>
    <name>J Huang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>告別2020</title>
    <link href="https://jhuang.me/2020/12/31/%E5%91%8A%E5%88%A52020/"/>
    <id>https://jhuang.me/2020/12/31/%E5%91%8A%E5%88%A52020/</id>
    <published>2020-12-31T19:06:40.000Z</published>
    <updated>2021-01-01T03:34:22.091Z</updated>
    
    <content type="html"><![CDATA[<style>.grid-container {    display: grid;    gap: 0em 4em;    grid-template-columns: 1fr 1fr;}</style><div class="grid-container"><p>近些時間我意識到，文本的線性順序給讀者施加了一層隱形的限制：即它更鼓勵讀者按照某一個順序閱讀文本。尤其在紙書上，翻頁是需要effort的。而人的感官，卻同時接收外部的訊號。即便是用來閱讀的眼睛，也在閱讀的情境中掃視周圍環境，或者逃離文字呆滯沈思。所以我希望把文本拆解，不安排任何一種順序，讀者自行組合片段，那便是每個人的獨特閱讀體驗了。</p><p>我播放著<a href="https://music.apple.com/us/album/the-first-no%C3%ABl/171434474?i=171434802">The First Noël</a>, 時間彷彿又回到幾天前的聖誕。今年休倫湖帶來了豐沛的降雪，新買的鴨子靴終於可以派上用場。踩在新雪上是嚓呲，遇到雪堆是嚓——喥喥——那是把鞋頭上的雪蹭掉，冰上是——，——躡手躡腳沒有聲音。往日的trail此時都模糊了邊界，人與車之間只靠黑色的雪團分割，它們是被剷雪機堆出來的泥土與雪的混合物。</p><p>我想起高行健的文字，於是在谷歌圖書中逡巡他的作品試讀樣章，他在《<a href="https://books.google.com/books?id=j-EvCwAAQBAJ">沒有主義</a>》一文中提到了他很欣賞商禽的文字，讀完《夢或者黎明》，不愧是高所謂「結構複雜的句子如歌一氣呵成」。然而等我知道台灣有這麼一位詩人的時候，他已經辭世十年了。可見信息的傳遞是多麼緩慢，獲得一個關鍵字又是多麼困難。</p></div>]]></content>
    
    
      
      
    <summary type="html">&lt;style&gt;
.grid-container {
    display: grid;
    gap: 0em 4em;
    grid-template-columns: 1fr 1fr;
}
&lt;/style&gt;

&lt;div class=&quot;grid-container&quot;&gt;
</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>文選上古點校本勘誤表</title>
    <link href="https://jhuang.me/2020/12/22/%E6%96%87%E9%81%B8%E4%B8%8A%E5%8F%A4%E9%BB%9E%E6%A0%A1%E6%9C%AC%E5%8B%98%E8%AA%A4%E8%A1%A8/"/>
    <id>https://jhuang.me/2020/12/22/%E6%96%87%E9%81%B8%E4%B8%8A%E5%8F%A4%E9%BB%9E%E6%A0%A1%E6%9C%AC%E5%8B%98%E8%AA%A4%E8%A1%A8/</id>
    <published>2020-12-22T19:51:23.000Z</published>
    <updated>2024-10-02T14:46:47.906Z</updated>
    
    <content type="html"><![CDATA[<p>注：本表不包含上古本與底本的字形差異，例如「既旣」「尚尙」等。如果遇到不能顯示的漢字，請安裝字庫，例如「中華書局宋體 02 平面」。本表只列上古整理過程中的錯字。</p><p>上古：上海古籍出版社 2019 年二版第一次印刷；上海古籍出版社 1986 年版，2011 年 9 月第九次印刷</p><p>胡刻：<a href="https://www.shuge.org/ebook/wen-xuan/">https://www.shuge.org/ebook/wen-xuan/</a></p><p>尤刻：中華書局 1977 景國家圖書館藏尤刻本，再造善本</p><p>考異嘉慶刊本：<a href="https://www.shuge.org/ebook/wen-xuan/">https://www.shuge.org/ebook/wen-xuan/</a></p><h2 id="錯字（文選及李善注）"><a href="#錯字（文選及李善注）" class="headerlink" title="錯字（文選及李善注）"></a>錯字（文選及李善注）</h2><table><thead><tr><th>篇名</th><th>上古 2019 本頁碼（1986 本）</th><th>上古</th><th>胡刻</th><th>尤刻</th><th>案</th></tr></thead><tbody><tr><td>卷二·西京賦「望北辰而高興」注</td><td>五八（五八）</td><td>齊，升也</td><td>隮，升也</td><td>隮，升也</td><td></td></tr><tr><td>卷五·吳都賦「灌注乎天下之半」注</td><td>二〇八（二〇四）</td><td>䃬，力罪切</td><td>𡾖，力罪切</td><td>𡾖，力罪切</td><td>正文即作「𡾖」</td></tr><tr><td>卷六·魏都賦「北臨漳滏」</td><td>二七一（2019 本已更正、二六六）</td><td>北臨漳㵚</td><td>北臨漳滏</td><td>北臨漳滏</td><td></td></tr><tr><td>卷六·魏都賦「授全模於梓匠」注</td><td>二七三（二六九）</td><td>僝，具也</td><td>𠊩，具也</td><td>𠊩，具也</td><td></td></tr><tr><td>卷六·魏都賦「秦餘徙㡂」注</td><td>二九八（二九四）</td><td>日南、北景、合浦</td><td>日南、比景、合浦</td><td>日南、比景、合浦</td><td></td></tr><tr><td>卷六·魏都賦「安得齊給守其小辯也哉」注</td><td>三〇二（二九八）</td><td>主無二王</td><td>土無二王</td><td>土無二王</td><td><a href="https://ctext.org/library.pl?if=gb&file=80195&page=104">禮記正義</a>亦作「土無二王」，胡刻、尤刻無誤</td></tr><tr><td>卷八·羽獵賦「遙噱乎紘中」</td><td>四〇三（三九五）</td><td>遙噱乎絃中</td><td>遙噱乎紘中</td><td>遙噱乎紘中</td><td>下文注中即為「紘」</td></tr><tr><td>卷九·北征賦「哀詩人之歎時」注</td><td>四三六（四二七）</td><td>毛詩序曰：大天久役</td><td>毛詩序曰：大夫久役</td><td>毛詩序曰：大夫久役</td><td><a href="https://ctext.org/library.pl?if=gb&file=80141&page=45">毛詩正義</a>亦作「大夫久役」，胡刻、尤刻無誤</td></tr><tr><td>卷十·西征賦「振皇綱而更維」注</td><td>四五六（四四八）</td><td>廓帝絃，恢皇綱</td><td>廓帝紘，恢皇綱</td><td>廓帝紘，恢皇綱</td><td>頁二〇一九《答賓戲》正作「廓帝紘恢皇綱」，胡刻、尤刻無誤</td></tr><tr><td>卷十·西征賦「筭嬴氏之利害」注</td><td>四五九（四五一）</td><td>天險不可升也。險，山川丘陵</td><td>天險不可升地險山川丘陵</td><td>天險不 □ 可升地險山川丘陵（ □ 為缺字）</td><td><a href="https://ctext.org/library.pl?if=en&file=80125&page=51">周易正義</a>作「天險不可升也地險山川丘陵也」</td></tr><tr><td>卷十·西征賦「九嵕嶻𡾲」</td><td>四六三（四五五）</td><td>嶻⿱山薛</td><td>嶻𡾲</td><td>嶻𡾲</td><td>陳八郎本、奎章閣本、茶陵本皆作「嶻嶭」，未知上古據何本所改</td></tr><tr><td>卷十·西征賦「望漸臺而扼腕」</td><td>四七〇（四六二）</td><td>望漸臺而扼晼</td><td>望漸臺而扼腕</td><td>望漸臺而扼腕</td><td></td></tr><tr><td>卷十一·登樓賦「倚曲沮之長洲」注</td><td>四九八（四八九）</td><td>而東南注于睢……睢與沮同</td><td>而東南注于雎……睢與沮同</td><td>而東南注于雎……睢與沮同</td><td>明本<a href="https://ctext.org/library.pl?if=gb&file=77823&page=34">山海經·中山經</a>正作「雎」</td></tr><tr><td>卷十二·海賦「磊匒匌而相豗」</td><td>五五六（五四六）</td><td>磊匒匌而相⿺兀豖</td><td>磊匒匌而相豗</td><td>磊匒匌而相豗</td><td>下文注中即爲「豗」</td></tr><tr><td>卷十二·海賦「擘洪波指太清」注</td><td>五五九（五四九）</td><td>舉首載五山</td><td>舉首載五山</td><td>舉首戴五山</td><td>藝文類聚、<a href="https://zh.wikisource.org/wiki/Page:Sibu_Congkan_Sanbian362-%E6%9D%8E%E6%98%89-%E5%A4%AA%E5%B9%B3%E5%BE%A1%E8%A6%BD-136-129.djvu/41">太平御覽·龜</a>作「戴」，<a href="https://zh.wikisource.org/wiki/Page:Sibu_Congkan_Sanbian350-%E6%9D%8E%E6%98%89-%E5%A4%AA%E5%B9%B3%E5%BE%A1%E8%A6%BD-136-117.djvu/44">太平御覽·釣</a>作「載」</td></tr><tr><td>卷十二·江賦標題小注</td><td>五六八（五五七）</td><td>璞以中興，三宅江外</td><td>璞以中興，王宅江外</td><td>璞以中興，王宅江外</td><td>六臣注作「三」，此據六臣注改</td></tr><tr><td>卷十二·江賦「三蝬𧉈江」「𧉈」音注</td><td>五七四（2019 本已更正、五六三）</td><td>𧉈⿰氵⿳亠𠃋个</td><td>𧉈⿰氵⿳亠𠃋个</td><td>𧉈流</td><td>「流」秦簡作「⿰氵⿳亠𠃋个」</td></tr></tbody></table><h2 id="錯字（考異部分）"><a href="#錯字（考異部分）" class="headerlink" title="錯字（考異部分）"></a>錯字（考異部分）</h2><table><thead><tr><th>篇名</th><th>上古 2019 本頁碼（1986 本）</th><th>上古</th><th>考異嘉慶刊本</th><th>備註</th></tr></thead><tbody><tr><td>卷二·西京賦注「蕢積也薛君曰蕢」</td><td>八六（2019 本已更正、八四）</td><td>薛君曰蔶積也</td><td>薛君曰蕢積也</td><td></td></tr><tr><td>卷三·東京賦注「太史順時視土」</td><td>一四六（一四二）</td><td>「視」當作「𤫽」</td><td>「視」當作「覛」</td><td></td></tr><tr><td>卷八·上林賦「青龍蚴蟉於東葙」</td><td>三八九（三八二）</td><td>竹卄每相混耳</td><td>竹艹每相混耳</td><td>「卄」為「廿」異體字，原文指偏旁，形似而迥異</td></tr><tr><td>卷八·上林賦「蜼玃飛𧕫」</td><td>三九一（三八三）</td><td>考集韻五旨，「䴎」下重文有六，而不載「蠝」</td><td>考集韻五旨，「䴎」下重文有六，而不載「𧕫」</td><td><a href="https://ctext.org/library.pl?if=gb&file=9471&page=38">集韻五旨䴎</a>有「蠝」無「𧕫」，考異無誤，上古本錯改</td></tr><tr><td>卷八·上林賦注「龍也無角」</td><td>三九一（三八四）</td><td>上卄者角也</td><td>上卝者角也</td><td>見「竹卄每相混耳」條</td></tr><tr><td>卷九·射雉賦「無見自䳮」</td><td>四三三（四二四）</td><td>「䳮」當作「𪃻」</td><td>「䳮」當作「𪅚」</td><td>底本此條第一葉均作「𪅚」，第二葉「𪅚」「𪃻」混用。考文中所引，<a href="https://ctext.org/library.pl?if=gb&file=9492&page=37">集韻二十一麥</a>載「䳮」，<a href="https://ctext.org/library.pl?if=gb&file=9492&page=59">集韻二十三錫</a>載「𪅚」不載「𪃻」，因此「𪃻」應訂正為「𪅚」。另「䳮」「𪃻」同字同音，不可能分屬集韻兩部</td></tr><tr><td>卷九·射雉賦「無見自䳮」</td><td>四三三（四二四）</td><td>二十三錫載「𪃻」字從「脈」</td><td>二十三錫載「𪅚」字從「脈」</td><td>應據<a href="https://ctext.org/library.pl?if=gb&file=9492&page=59">集韻二十三錫</a>校對為「𪅚」字從「眽」</td></tr><tr><td>卷十·西征賦注「閿鄉縣東十里鳩澗西」</td><td>四八九（四八〇）</td><td>泉鳩水今在閿鄉縣</td><td>泉鳩水今在䦩鄉縣</td><td>注引<a href="https://ctext.org/library.pl?if=gb&file=79544&page=161">後漢書戾太子傳顏師古注</a>作「闅」，「闅」「䦩」同。</td></tr></tbody></table><h2 id="版式錯誤"><a href="#版式錯誤" class="headerlink" title="版式錯誤"></a>版式錯誤</h2><p>二三九（2019 本已更正，二三五），「文選異考」，當為「文選考異」</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;注：本表不包含上古本與底本的字形差異，例如「既旣」「尚尙」等。如果遇到不能顯示的漢字，請安裝字庫，例如「中華書局宋體 02 平面」。本表只列上古整理過程中的錯字。&lt;/p&gt;
&lt;p&gt;上古：上海古籍出版社 2019 年二版第一次印刷；上海古籍出版社 1986 年版，2011 年 </summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>記夢</title>
    <link href="https://jhuang.me/2019/12/27/%E8%A8%98%E5%A4%A2/"/>
    <id>https://jhuang.me/2019/12/27/%E8%A8%98%E5%A4%A2/</id>
    <published>2019-12-27T14:47:03.000Z</published>
    <updated>2019-12-27T15:19:36.699Z</updated>
    
    <content type="html"><![CDATA[<p>我坐在妳的桌子上和人聊天，妳不在旁邊。</p><p>我離開去拿一個水壺，回來的時候看到妳，妳說要用這個桌子須要先輸入一個密碼。</p><p>我料想這還不簡單，因為她曾經告訴過我妳的密碼，作為曾經幫助過她們的回報。671531，妳搖了搖頭，密碼不對。</p><p>我疑惑地又想了一遍，不可能會錯的！啊，我恍然大悟，我只是記錯了一位數字而已，為什麼就不能再試一次。</p><p>我撇過頭看看她，她明明告訴過我密碼，她笑一笑，愛莫能助。</p><p>我打開妳的QQ群，我還在群裡面，她還是管理員。忽然群裡彈出了一個提示，她允許了一個新的詞語的使用。</p><p>我要離開這個地方，這樣我就不會感覺到我被妳拒絕了。</p><p>我提著行李站在走廊上，忽然發現妳和她也要離開。我錯愕不已，這讓我的離開變得沒有意義。</p><p>我知道這是我最後一次看見妳，我想如果我看著妳的時間比妳看見我長，那我就不會有遺憾。</p><p>妳看到了我在看妳。</p><p>足以讓我在十年後從夢裡驚醒過來。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我坐在妳的桌子上和人聊天，妳不在旁邊。&lt;/p&gt;
&lt;p&gt;我離開去拿一個水壺，回來的時候看到妳，妳說要用這個桌子須要先輸入一個密碼。&lt;/p&gt;
&lt;p&gt;我料想這還不簡單，因為她曾經告訴過我妳的密碼，作為曾經幫助過她們的回報。671531，妳搖了搖頭，密碼不對。&lt;/p&gt;
&lt;p&gt;我疑惑</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>CS 61C Performance Contest</title>
    <link href="https://jhuang.me/2019/08/14/CS-61C-Performance-Contest/"/>
    <id>https://jhuang.me/2019/08/14/CS-61C-Performance-Contest/</id>
    <published>2019-08-14T20:22:44.000Z</published>
    <updated>2019-08-15T14:34:44.242Z</updated>
    
    <content type="html"><![CDATA[<p><image alt="Achievement unlocked: Beat the staff" class="lozad" data-src="/img/SS_0002.png"></image><br>The CS 61C Performance Contest is a project where students will optimize given <a href="https://github.com/61c-teach/su19-proj4-starter">C code</a>, this time a convolutional neural network, as fast as possible. I did a solo and finally achieved 29.968x speed up and got the extra <em>beat-the-staff</em> credits. It was a wonderful journey and I would like to share about some techinques I used.</p><a id="more"></a><p><image alt="Gradescope result" src="/img/SS_0001.png"></image><br>Disclaimer: I am not a UC Berkeley student and I don’t have access to the hive machines. Although the project states that the code need to be run on a hive machine but there is no major obstacles debugging and running on a unix-like OS, i.e. macOS, as long as your CPU supports Haswell assembly instructions. The dataset on the hive machine is also public available: I download the <a href="https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz">CIFAR dataset</a> and run the code on a Macbook Pro 2016.</p><h2 id="Profiling"><a href="#Profiling" class="headerlink" title="Profiling"></a>Profiling</h2><p>The project introduces some useful <code>gprof</code> commands for profiling. Unforturnately <code>gprof</code> is <a href="https://apple.stackexchange.com/a/154324">not supported</a> on macOS, so I uses <code>pprof</code> instead, which can generate similar <code>gprof</code>-like flat profile.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ cc -lm -lprofiler -Wall -Wpedantic -Wextra -march&#x3D;haswell -std&#x3D;c99 -fopenmp -O3 -o benchmark benchmark.o network.o layers.o volume.o</span><br><span class="line">$ .&#x2F;benchmark benchmark 1200 CPUPROFILE&#x3D;&#x2F;tmp&#x2F;prof.out</span><br><span class="line">$ pprof --top .&#x2F;benchmark &#x2F;tmp&#x2F;prof.out</span><br></pre></td></tr></table></figure><p>Here is an (incompleted) output example</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> flat  flat%   sum%        cum   cum%</span><br><span class="line">0.03s  0.51% 99.32%      0.03s  0.51%  _posix_madvise</span><br><span class="line">0.01s  0.17% 99.49%      0.20s  3.42%  _load_batch</span><br></pre></td></tr></table></figure><p>You should interpret the profile to find out which routine occupies most percents of running time.</p><blockquote><p>A beard well lathered is half shaved.</p></blockquote><p>The profiling command is used so often that I would recommend to write your own Makefile rule, i.e. <code>make benchmark_profile</code> from the very begining so that you can profile your program with minimum cognitive loads.</p><h2 id="Common-Techniques"><a href="#Common-Techniques" class="headerlink" title="Common Techniques"></a>Common Techniques</h2><h5 id="Hoist-memory-operations"><a href="#Hoist-memory-operations" class="headerlink" title="Hoist memory operations"></a>Hoist memory operations</h5><p>Hoise memory operations in nested loops if you know they are <em>not</em> changed inside the loop. For example,</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; i++) &#123;</span><br><span class="line">    baz();</span><br><span class="line">    foo-&gt;bar-&gt;quz[i] = i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The compiler would not know whether <code>foo-&gt;bar-&gt;quz</code> points to the same memory location in the loops. Therefore it would generate three <code>mov</code> (<code>lw</code> in RISC-V) inside the loop. If you, <em>Homo sapiens</em>, know in advance that <code>foo-&gt;bar-&gt;quz</code> is a <a href="https://en.wikipedia.org/wiki/Loop-invariant_code_motion">loop invariant</a>, please explicitly convey the fact to the compiler by hoisting the memory operation.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>* quz = foo-&gt;bar-&gt;quz;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; i++) &#123;</span><br><span class="line">    baz();</span><br><span class="line">    quz[i] = i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>There is a common misunderstanding that you should <a href="https://carter.sande.duodecima.technology/cs61c-performance/">hoist everything you can</a>. No! Any modern optimizer will hoist simple math operations for you, (but not memory operation since they are not semantically equivalent). For example you should not worry about whether you need to hoist <code>j * j</code>, the optimizer will do this <a href="https://godbolt.org/z/obVD_h">automatically</a>. Hoisting every simple math operation simply wastes your precious time.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>* quz = foo-&gt;bar-&gt;quz;</span><br><span class="line"><span class="keyword">int</span> j = <span class="number">42</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; i++) &#123;</span><br><span class="line">    baz();</span><br><span class="line">    quz[i] = i + j * j;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="Eliminate-branches"><a href="#Eliminate-branches" class="headerlink" title="Eliminate branches"></a>Eliminate branches</h5><p>Scrutinize the branch operations in deeply nested loops, especially those conditions related to the loop counter only. You can replace branch by carefully chosen counter ranges.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (i &gt;= m &amp;&amp; i &lt; n) &#123;</span><br><span class="line">        <span class="comment">/* do something on i */</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In the example above, we can get rid of the <code>if</code> branch by having i loop from <code>min(0, m)</code> to <code>max(1&lt;&lt;16, n)</code>.</p><h5 id="Minimize-number-of-variables-involved-in-deep-loops"><a href="#Minimize-number-of-variables-involved-in-deep-loops" class="headerlink" title="Minimize number of variables involved in deep loops"></a>Minimize number of variables involved in deep loops</h5><p>Use your Math skills to minimize the variables inside deep loops. For example, let’s say you have a 8-register CPU runing the following code</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">loop1:</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; i++) &#123;</span><br><span class="line">    <span class="comment">/* do something with j, k, l, m, p */</span></span><br><span class="line">    q = m * k;</span><br><span class="line">    loop2:</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> n = <span class="number">0</span>; n &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; n++) &#123;</span><br><span class="line">        quz = (n + m) * k </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* do something with j, k, l, m, p, q */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Before jumping to <code>loop2</code> with four variables involved, the compiler have to put at lease one register used in the <code>loop1</code> onto the stack, and restore after <code>loop2</code> is finished. This process is called <a href="https://en.wikipedia.org/wiki/Register_allocation">register allocation</a>.</p><p>Apparently <code>quz = (n + m) * k</code> can be simplified to <code>quz += k</code> if one initializes <code>quz = q</code>.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; i++) &#123;</span><br><span class="line">    <span class="comment">/* do something with j, k, l, m, p */</span></span><br><span class="line">    q = m * k;</span><br><span class="line">    loop2:</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> n = <span class="number">0</span>, quz = q; n &lt; <span class="number">1</span> &lt;&lt; <span class="number">16</span>; n++, quz += k) &#123; &#125;</span><br><span class="line">    <span class="comment">/* do something with j, k, l, m, p, q */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In the latter cases <code>loop2</code> still has 4 variables (<code>n</code>, <code>quz</code>, <code>k</code>, <code>q</code>), but since <code>q</code> is reused, we don’t have to save anything on the stack,which means, let’s say <code>p</code> is in <code>%rbx</code> you have saved 65536 <code>push %rbx</code> and 65536 <code>pop %rbx</code> instructions. </p><p>As a byproduct you have also <a href="https://en.wikipedia.org/wiki/Strength_reduction">reduced</a> the computation strength of <code>(n + m) * k</code> to <code>quz + k</code>, but the bottleneck here is <code>mov</code> since the <a href="https://gamedev.stackexchange.com/a/104534">latency difference</a> of addition (0.5) and multiplication (1.75) is smaller than <a href="https://electronics.stackexchange.com/a/104761">L1 Cache latency</a> (3).</p><h5 id="Inline-small-static-functions-copy-amp-paste-if-compiler-doesn’t"><a href="#Inline-small-static-functions-copy-amp-paste-if-compiler-doesn’t" class="headerlink" title="Inline small static functions, copy &amp; paste if compiler doesn’t"></a>Inline small static functions, copy &amp; paste if compiler doesn’t</h5><p>Function with many arguments will incur spill and reload register pressure, which means you will have much more <code>mov</code> in the prologue and epilogue of the called function. You may know that in C we could declare that a function is <code>inline</code> so the compiler will try to merge the intructions of this function to wherever it is called. But any modern optimizier will automatically inline small functions even if you don’t specify it is <code>inline</code>.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;helpers.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* will be automatically inline in most cases */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">foo</span><span class="params">(<span class="keyword">int</span> x)</span> </span>&#123; <span class="keyword">return</span> x + <span class="number">1</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    foo(<span class="number">0</span>);</span><br><span class="line">    <span class="comment">/* would not be inline if bar is defined in helpers.h */</span></span><br><span class="line">    bar();</span><br><span class="line">    <span class="keyword">return</span> foo(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, there are some exception cases where the compiler could <em>not</em> inline the source. For example, if a small function is defined in other headers, the compiler would not inline this function since it is in another object file. But we know it is small and static, so we can simply copy paste these functions so that our critical parts will execute an inline version of these small helpers. By doing this we can get rid of the function overhead: extra spills and reloading stack memory access.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;helpers.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* copy pasted from helpers.h */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">bar</span><span class="params">(<span class="keyword">int</span> x)</span> </span>&#123; <span class="keyword">return</span> x + <span class="number">2</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* will be automatically inline in most cases */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">foo</span><span class="params">(<span class="keyword">int</span> x)</span> </span>&#123; <span class="keyword">return</span> x + <span class="number">1</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    foo(<span class="number">0</span>);</span><br><span class="line">    <span class="comment">/* would be inline since bar is defined in same object file */</span></span><br><span class="line">    bar();</span><br><span class="line">    <span class="keyword">return</span> foo(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>You may have a sense now our performance optimization actually focus on the number of memory operations. And yes visiting memory/cache is way slower than computation. The more <code>mov</code> you have reduced, the more performance gain you get.</p><h2 id="OpenMP"><a href="#OpenMP" class="headerlink" title="OpenMP"></a>OpenMP</h2><p>Although the <code>OpenMP</code> section is in the last. Personally I recommend to apply OpenMP in the earlier stage as long as you figure out where to add OpenMP pragmas. It should be added to the biggest parallelable computation units with minimum input variables. Once you have correctly configured OpenMP, you will have an independently 5-6x boost on a 4-core 8-threads machines. Combining <code>OpenMP</code> with the common techniques will give you 12x boost.</p><p>If you are running project on macOS like me, you may be teriffied by the fact that every <code>partest</code> fails on macOS. Note that <code>partest</code> simply tests your program against a <code>rand()</code> indexed of datasets (<a href="https://github.com/61c-teach/su19-proj4-starter/blob/master/benchmark.c#L274">code</a>), and macOS has different <code>rand()</code> implementation to Linux. So you have to generate the <code>partest</code> output from reference implementation yourself.</p><p>I have tried different <a href="https://www.openmp.org/wp-content/uploads/SC17-Kale-LoopSchedforOMP_BoothTalk.pdf">OpenMP schedulers</a> but they don’t differ a lot. I think we’d better keep it simple.</p><h2 id="Unrolling"><a href="#Unrolling" class="headerlink" title="Unrolling"></a>Unrolling</h2><p>A modern optimizer will do <a href="https://en.wikipedia.org/wiki/Loop_unrolling">loop unrolling</a> for you so you don’t have to unroll if you don’t have a good reason, that is, you know nothing more than the compiler. I will mentioned unrolling later.</p><h2 id="Specialization"><a href="#Specialization" class="headerlink" title="Specialization"></a>Specialization</h2><p>Before talking about SIMD instructions, I would like to shred on specialization. A way that you convery extra information to the compiler. Let’s say if the critical part is vector operator: dot product, the compiler would not know the length of the vector, but you can always print at runtime to see if it reveals any pattern. After I reach 12x, I have been stuck for a while. It is a break through when I notice that the length of vector is actually discrete. I added a switch case to delegate dotProduct to different versions: 3x3, 16x16 and 20x20. The specializaed dotProducts get rid of the deepest for loop and we don’t need allocate register to the loop counter now.</p><h2 id="SIMD-instructions-or-Assembly-Programmer"><a href="#SIMD-instructions-or-Assembly-Programmer" class="headerlink" title="SIMD instructions or Assembly Programmer"></a>SIMD instructions or Assembly Programmer</h2><h5 id="Hotpath"><a href="#Hotpath" class="headerlink" title="Hotpath"></a>Hotpath</h5><p>Both SIMD version of 16x16 and 20x20 dot product are straighforward to implement, for 3x3 dot product I leave is as-is because I don’t find any significant speed improvement. The SIMD version of 16x16 and 20x20 will boost performance upto 16x. You may relax since then, actually some students stop here according to the <a href="https://github.com/search?q=conv_forward_3&type=Code">search result</a> of github.</p><p>But if you want to beat the staff like what I did, it is only the begining. It is extremely important to profile how often or the percentage of time spend on <code>dotProduct3_3</code>, <code>dotProduct16_16</code> and <code>dotProduct20_20</code>. You may find that the helper function <code>dotProduct16_16</code> is inlined in <code>-O3</code> but we can use directive to tell the compiler do not inline these functions.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">__attribute__((noinline))</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">dotProduct3_3</span><span class="params">(<span class="keyword">double</span> * dst, <span class="keyword">const</span> <span class="keyword">double</span> * dp_x, <span class="keyword">const</span> <span class="keyword">double</span> * dp_y)</span></span></span><br></pre></td></tr></table></figure><p>Now you can see the benchmark</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">0.35s  8.86% 64.05%      0.35s  8.86%  _dotProduct16_16</span><br><span class="line">0.25s  6.33% 70.38%      0.25s  6.33%  _dotProduct3_3</span><br><span class="line">0.08s  2.03% 93.42%      0.08s  2.03%  _dotProduct20_20</span><br></pre></td></tr></table></figure><p>Apparently, <code>dotProduct16_16</code> and <code>dotProduct3_3</code> is the hot path.</p><h5 id="Devtools-Make-a-profiler-switch"><a href="#Devtools-Make-a-profiler-switch" class="headerlink" title="Devtools: Make a profiler switch"></a>Devtools: Make a profiler switch</h5><p>Note that you would soon find it cumbersome to switch <code>__attribute__((noinline))</code> back and forth, thanks to C preprocessor we can define a <code>__MODIFIER__</code> macro</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> _PROFILE_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __MODIFIER__ __attribute__((noinline))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __MODIFIER__</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">__MODIFIER__</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">dotProduct3_3</span><span class="params">(<span class="keyword">double</span> * dst, <span class="keyword">const</span> <span class="keyword">double</span> * dp_x, <span class="keyword">const</span> <span class="keyword">double</span> * dp_y)</span></span></span><br></pre></td></tr></table></figure><p>and create a <code>profile-switch.h</code> which simply define the <code>_PROFILE_</code> macro</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _PROFILE_</span></span><br></pre></td></tr></table></figure><p>Then you can include <code>profile-switch.h</code> only when it is profiled.</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">CC=/usr/local/opt/llvm/bin/clang</span><br><span class="line">CFLAGS?=-Wall -Wpedantic -Wextra -march=haswell -std=c99 -fopenmp -O3</span><br><span class="line">PROFILE_CFLAGS=-lprofiler -Wall -Wpedantic -Wextra -march=haswell -std=c99 -fopenmp -O3</span><br><span class="line"></span><br><span class="line">benchmark_profile : benchmark.o network.o layers.o_profile volume.o</span><br><span class="line">    <span class="variable">$(CC)</span> <span class="variable">$(PROFILE_CFLAGS)</span> -o benchmark benchmark.o network.o layers.o volume.o -lm</span><br><span class="line">CPUPROFILE=/tmp/prof.out ./benchmark benchmark</span><br><span class="line">pprof --top ./benchmark /tmp/prof.out</span><br><span class="line"></span><br><span class="line">layers.o_profile : layers.c layers.h volume.h</span><br><span class="line"><span class="variable">$(CC)</span> <span class="variable">$(CFLAGS)</span> <span class="keyword">-include</span> profile-switch.h -c layers.c -o layers_profile.o</span><br></pre></td></tr></table></figure><h5 id="Destroy-small-loops"><a href="#Destroy-small-loops" class="headerlink" title="Destroy small loops"></a>Destroy small loops</h5><p>I have mentioned that a SIMD version of <code>dotProduct3_3</code> does not significantly improve performance. Let’s analyse it a bit. In <code>dotProduct3_3</code> we will do two <code>vmovupd</code> (<code>_mm256_loadu_pd</code>) to load 4 64bit-memory cells, interpreted as double, into two YMM registers. But we are only computing a 3x3 dot product: we wasted 1/4 of the memory bandwidth!</p><p>If you take a closer look to the <code>dotProduct3_3</code> loop, you will find the memory access location is continuous, i.e.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dotProduct3_3(dst, a[<span class="number">0</span>], b[<span class="number">0</span>]);</span><br><span class="line">dotProduct3_3(dst, a[<span class="number">3</span>], b[<span class="number">3</span>]);</span><br><span class="line">dotProduct3_3(dst, a[<span class="number">6</span>], b[<span class="number">6</span>]);</span><br></pre></td></tr></table></figure><p>which means a loop of five <code>dotProduct3_3</code> operation is equivalent to a <code>dotProduct15_15</code> operation.</p><p>By checking the runtime states we can find that the loop bound is discrete, too. Profiling reveals that <code>dotProduct15_15</code> is the new hot path. We can estimate that a <code>dotProduct15_15</code> is 25% faster than 5 consequent <code>dotProduct3_3</code>. Why? Because we just need 8 <code>vmovupd</code> for <code>dotProduct15_15</code>, which is bounded by <code>dotProduct16_16</code>.</p><details><summary>You may expand the details if you are interested at the tail-case processing</summary><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">__m256d x_tail = _mm256_loadu_pd(x[<span class="number">11</span>]); <span class="comment">// x[14], x[13], x[12], x[11]</span></span><br><span class="line"></span><br><span class="line">x_tail = _mm256_blend_pd(x_tail, _mm256_zero_pd(), <span class="number">0x1</span>); <span class="comment">// x[14], x[13], x[12], 0</span></span><br><span class="line"></span><br><span class="line">_mm256_mul_pd(x_tail, _mm256_loadu_pd(y[<span class="number">11</span>]);</span><br><span class="line"><span class="comment">// returns x[14] * y[14], x[13] * y[13], x[12] * y[12], 0</span></span><br></pre></td></tr></table></figure></details><p>In a way it is also a kind of unrolling with one loop only, but we have done more than that and reduces the number of <code>vmovupd</code>.</p><p>Some extra questions to think about: Do we need rewrite three <code>dotProduct3_3</code> to <code>dotProduct9_9</code>? Do we need rewrite three <code>dotProduct16_16</code>? How about <code>dotProduct20_20</code>?</p><p>This change brings me 22x boost on Aug. 9.</p><h2 id="Unrolling-or-abusing-the-precompiler"><a href="#Unrolling-or-abusing-the-precompiler" class="headerlink" title="Unrolling or abusing the precompiler?"></a>Unrolling or abusing the precompiler?</h2><p>The next thing I tried is unrolling the <code>dotProduct16_16</code>. Note that rewriting <code>dotProduct16_16</code> to larger <code>dotProduct</code> does not benefit a lot since we can not decrease the number of <code>vmovupd</code>, it is optimal. But we can still get rid of loop by unrolling. The <code>dotProduct16_16</code> is inside of number 3/4/5 loops. I ended up writing a macro which simply expands to a for loop with fixed bound. It is like</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> dotProductRoutine16_16(Round) &#123;\</span></span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; Round; i++) &#123;\</span><br><span class="line">    dotProduct16_16(dotProductSum, a[<span class="comment">/* index from i */</span>], b[<span class="comment">/* index from i */</span>]);\</span><br><span class="line">  &#125;\</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span>(round) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">        dotProductRoutine16_16(<span class="number">5</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">        dotProductRoutine16_16(<span class="number">4</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        dotProductRoutine16_16(<span class="number">3</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The compiler does a good job optimizing those fixed loops and I have got 25x. But I knew from <a href="https://docs.google.com/presentation/d/1_OY7wvM8Q-cxIy4kjUkwZl85rZphdCJ4/edit#slide=id.p37">the comments</a> that our staff has come up with a 27x in hand. So I should definitely do more to get extra credits.</p><h2 id="Memory-access"><a href="#Memory-access" class="headerlink" title="Memory access"></a>Memory access</h2><p>For the convenience of writing organization, I have placed memory access in an indepedent section. Actually I keeps thinking if there is anything I can improve on general memory access pattern other than the convolution step. It turns out there are several aspects you can look into:</p><ul><li>Try to print out the assignment values after a big <code>malloc</code>, if it is always zero, consider switch to hardware-friendly <code>calloc</code>. In macOS the compiler generates a special <code>__platform_bzero$VARIANT$Haswell</code> call which is way faster than accessing the memory and write only zero back. Yes we are still decreasing the number of <code>mov</code>.</li><li>If sometimes the assignment is zero and sometimes not, consider switch to a <code>memset</code> and write to the memory only when it is <em>not</em> zero.</li><li>Are there any loop pattern results to a high cache miss rate? For example are there any write steps where a whole cache line is loaded but only written one value? Consider to modify loop levels to leverage CPU caches.</li></ul><p>All these efforts pay off: It brings me to 29.968x in Aug. 12</p><h2 id="Performance-Reports-on-my-local-machines"><a href="#Performance-Reports-on-my-local-machines" class="headerlink" title="Performance Reports on my local machines"></a>Performance Reports on my local machines</h2><p>Flat profile of classifying 1200 images</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"> flat  flat%   sum%        cum   cum%</span><br><span class="line">1.15s 64.97% 64.97%      1.16s 65.54%  _conv_forward</span><br><span class="line">0.22s 12.43% 77.40%      0.22s 12.43%  __platform_bzero$VARIANT$Haswell</span><br><span class="line">0.19s 10.73% 88.14%      0.19s 10.73%  _swtch_pri</span><br><span class="line">0.07s  3.95% 92.09%      1.43s 80.79%  [libomp.dylib]</span><br><span class="line">0.04s  2.26% 94.35%      0.04s  2.26%  _posix_madvise</span><br><span class="line">0.03s  1.69% 96.05%      0.03s  1.69%  _read$NOCANCEL</span><br><span class="line">0.02s  1.13% 97.18%      0.02s  1.13%  _pool_forward</span><br><span class="line">0.01s  0.56% 97.74%         1s 56.50%  _.omp_outlined.</span><br><span class="line">0.01s  0.56% 98.31%      0.01s  0.56%  __kernelrpc_mach_vm_deallocate_trap</span><br><span class="line">0.01s  0.56% 98.87%      0.04s  2.26%  _free_small</span><br><span class="line">0.01s  0.56% 99.44%      0.01s  0.56%  _lseek</span><br><span class="line">0.01s  0.56%   100%      0.01s  0.56%  _relu_forward</span><br></pre></td></tr></table></figure><p>The other forward operation has been optimized for memory access. <code>_conv_forward</code>, <code>__platform_bzero$VARIANT$Haswell</code>(zero-filling the memory), <code>_swtch_pri</code>(macOS libsystem_kernel calls) are the top-3 computing intensive calls.</p><p>Comparison to baseline: classifying 2400 images</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">.&#x2F;benchmark benchmark 2400</span><br><span class="line">RUNNING BENCHMARK ON 2400 PICTURES...</span><br><span class="line">Making network...</span><br><span class="line">Loading batches...</span><br><span class="line">Loading input batch 0...</span><br><span class="line">Running classification...</span><br><span class="line">79.083333% accuracy</span><br><span class="line">820224 microseconds</span><br><span class="line">.&#x2F;benchmark_baseline benchmark 2400</span><br><span class="line">RUNNING BENCHMARK ON 2400 PICTURES...</span><br><span class="line">Making network...</span><br><span class="line">Loading batches...</span><br><span class="line">Loading input batch 0...</span><br><span class="line">Running classification...</span><br><span class="line">79.083333% accuracy</span><br><span class="line">36103328 microseconds</span><br></pre></td></tr></table></figure><p>When comparing more than 1200 images, the thread overhead is ammortized and we could get a better speed up to 40x.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Performance programming is like collaborating with the compiler: you want to feed more facts into compiler so that it can help you get rid of superfluous memory operations. I learned C ten years ago and only in this project, and the other projects in CS-61C that I feel connections with this so called stone-age language. Its great flexibility and straight-forward memory layout enable various way to utilize the hidden power inside our hardware.</p><p>A beard well lathered is half shaved. Always spend some time on your development tools: scaffolds, mnemonics, command + R… Switching context not only gives your brain a break but also keep the fire burning so that you will not be blamed by yourself: I ended up doing no commits in a whole day!</p><p>CS 61C is the first course that I have spent so much efforts after I was graduated. I can’t believe I could finish all the projects and homeworks without access to TA and hive machines. It has inspired me a lot and proved to myself my learning abilities. Thank you <a href="https://cs61c.org/staff/">CS 61C staffs</a> for offering me such a great course and intriguing projects. Thank you my friend Wèi Cōngruì for dicussions on memory layout issues. Thank you University of Waterloo for offering a working environment to a new immigrant like me, for free.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;image alt=&quot;Achievement unlocked: Beat the staff&quot; class=&quot;lozad&quot; data-src=&quot;/img/SS_0002.png&quot;&gt;&lt;/image&gt;&lt;br&gt;The CS 61C Performance Contest is a project where students will optimize given &lt;a href=&quot;https://github.com/61c-teach/su19-proj4-starter&quot;&gt;C code&lt;/a&gt;, this time a convolutional neural network, as fast as possible. I did a solo and finally achieved 29.968x speed up and got the extra &lt;em&gt;beat-the-staff&lt;/em&gt; credits. It was a wonderful journey and I would like to share about some techinques I used.&lt;/p&gt;</summary>
    
    
    
    
    <category term="C, performance, CNN" scheme="https://jhuang.me/tags/C-performance-CNN/"/>
    
  </entry>
  
  <entry>
    <title>芝加哥 Abaddon Prime 大战回忆</title>
    <link href="https://jhuang.me/2019/05/29/chicago-abbadon-prime/"/>
    <id>https://jhuang.me/2019/05/29/chicago-abbadon-prime/</id>
    <published>2019-05-30T00:33:50.672Z</published>
    <updated>2019-06-03T12:33:49.179Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/img/abaddon_registration.png" alt="Abaddon Prime"></p><!--- more ---><h2 id="现场签到"><a href="#现场签到" class="headerlink" title="现场签到"></a>现场签到</h2><p>从 Clark/Lake 地铁口出来，沿着湖街直走，登上 AON 中心的空中走廊，穿过哥伦比亚大道。两边行人的衣服色调渐趋两极，非蓝即绿。但所有人都步履缓慢，他们的眼睛锁着挂上了充电宝的屏幕，他们的手指机械地飞舞着。我眼前一黑，抬起头，一辆黑色大巴拦住了我。</p><p><img src="/img/IMG_6805.jpg" alt="NL1331"></p><p>就是这辆贴着 NL-1331 的面包车，附带一个名为<code>#NL133X</code> 的 portal。NL-1331 的官方介绍如下：</p><blockquote><p>NL-1331 features casual missions aiming to explore different cities and locales using a special Ingress-customized vehicle called the “XM Detection Mobile Lab.”</p></blockquote><p>NL-1331 就是一个叫做 XM 检测仪移动实验室的到处跑的 po，上面还有非常随便的只有一个 portal 的任务。从车牌号可以看出来，这是从加州总部运过来的小车。</p><p>顺手摸了两个 key 以后，正好前面一个 PLMM 问一个小伙子：</p><p>「Where is the registration portal?」</p><p>「注册 po 就在大堂里面。」</p><p>进酒店之后一道紫光闪过，这就是注册的地方了。</p><p><img src="/img/IMG_6807.jpg" alt="Check-in"></p><p>注册完后会拿到了 Abaddon Prime 的小信封，看起来和高雄、阿姆斯特丹前两场的信封没有任何区别。</p><p><img src="/img/IMG_6809.jpg" alt="Envelope"></p><p>会场还有 Ingress Animation 的主角留影板</p><p><img src="/img/IMG_6810.jpg" alt="IngressAnimation"></p><p>但是注册 po 才是签到的坑点</p><p><img src="/img/IMG_6806.jpg" alt="Registration Portal"></p><p>进去大堂 50 米，手机信号彻底没有。出来大堂之后，有信号定位已经不对了，沿着硕大的酒店大楼绕了一圈，依旧碰不到注册 po。最终转了两圈之后，找到了北蓝「小伙子」@BGDajiu，放了一个 Wi-Fi 热点，终于 hack 了注册 po，拿到 media。立马打 Uber 前往我们鹰队负责的 D 战区。</p><h2 id="D-战区：大战前的控毒"><a href="#D-战区：大战前的控毒" class="headerlink" title="D 战区：大战前的控毒"></a>D 战区：大战前的控毒</h2><p><a href="https://anomaly-map.com/">Anormaly Map</a> 应用提供了芝加哥战区地图，给每个 po 分了区域和编号，方便指挥。下图就是 D 战区的位置。</p><p><img src="/img/IMG_7140.jpg" alt="DZone"></p><p>到集合地点的时候已经是 11:58，队长指示 12:00 把 D14 翻绿。一路小跑终于在 12:00 赶到 D14，却发现毒还在桶里，于是哆哆嗦嗦从桶里掏出了陈年绿毒，翻绿了之后在备忘录记下了时间，12:01。</p><p>事后看来，毒的时间需要十分精准的记录，精确到秒是必须的。否则当毒的免疫时期过了再次翻毒的时候，就会算不准免疫时间而使得毒被浪费掉。毒完回到集合点的时候，大家开始了愉快的 Pokemon Go 加好友交换 Pokemon 活动。我和 @Ma1aKai 换了一个 Pokemon Go 社区活动出来的 Unknown，显摆一下。</p><p><img src="/img/IMG_6817.jpg" alt="Unknown"></p><p>12:45 的时候，D 战区已经被蓝军全部打蓝，准备13:00的翻毒。毒完之后我们趁着半个小时的空闲时间，赶紧去吃一顿午饭。</p><p><img src="/img/IMG_6823.jpg" alt="Aloha"></p><p>在吃饭的同时，我也在 Prime 上注意到绿军在 D 战区已经开始连起来做 field 了。我问队长这是否是某种策略，队长说没什么这就是 xjbl。事后看来，绿军已经使用了 Carrie’s journal 提前获得了 Anormaly 第一个测量点的 research portal，而这其中就有 D区的 D22 po。1点多绿军连起来这些线就是把 D22 保护起来，以便在开赛前可以全部清掉，让其它 po 在第一个测量点之前可以连到 D22。</p><h2 id="超新星的形成：碎片战"><a href="#超新星的形成：碎片战" class="headerlink" title="超新星的形成：碎片战"></a>超新星的形成：碎片战</h2><p>吃完饭后，蓝军绿军都开始聚集到 D22 po，大家都知道这里开战后会有事情发生。同时，绿军也开始从各个方向连到 D22，当时的场面是这样的：</p><p><img src="/img/IMG_6824.jpg" alt="D22"></p><p>大战开始后，这里果不其然成为了绿军的研究 portal（会有一个黄色的小三角形）</p><p><img src="/img/IMG_6826.jpg" alt="D22 @ 14:32"></p><p>许多碎片也开始被传到这个球门。<br><img src="/img/IMG_6827.jpg" alt="D22 @ 14:36"></p><p>而在此后的一个小时，D战区一直被绿军统治，贴个图体会一下啥叫绝望。</p><p><img src="/img/IMG_6830.jpg" alt="D22 @ 15:27"></p><p>15:30 鹰队退出 D 战区，后勤大姐的 food cart 准时停在路边，各位特工蜂拥而至。</p><p><img src="/img/IMG_6831.jpg" alt="food cart"></p><p>零食、水、急救包一应俱全。</p><p><img src="/img/IMG_6832.jpg" alt="food cart interior"></p><p>一番补给之后，我们上了一辆神秘的大巴。</p><h2 id="UPH——移动的摸-po-机"><a href="#UPH——移动的摸-po-机" class="headerlink" title="UPH——移动的摸 po 机"></a>UPH——移动的摸 po 机</h2><p><img src="/img/IMG_6835.jpg" alt="Ideal Chapter"></p><p>此前我在<a href="https://bjres.net/2018/11/19/%E5%BF%85%E8%BE%93%E7%9A%84-xma-%E4%BD%93%E9%AA%8C%E6%B3%A8%E5%AE%9A%E5%BE%88%E5%B7%AE%EF%BC%9F/">必输的 XMA 体验注定很差？</a>一文中得知凤凰城 XMA 是有 SUV 负责 UPH 之旅，所以对有专车负责 UPH 这事不出奇，但大巴都动用了还是让我觉得非常意外，蓝军果真是大投入啊。</p><p>于是我们坐着大巴，听着 ADA，吹着空调，喝着冰水，一路 hack。我刚上车的时候还不知道这是一个秘密行动，放了一些多余的八炸清仓，被后面的大哥「Don’t Attack」喝止了。一路上收集着 Artifacts Portal 掉落的 Media。</p><p>大巴策略让蓝军的 Artifacts 数量和 UPH 都超过了绿军。深蓝 @CrytoGL 评论得好：「现在人数或者战斗力劣势方都是这么玩了，战术另寻蹊跷」。</p><h2 id="最后也要顽强"><a href="#最后也要顽强" class="headerlink" title="最后也要顽强"></a>最后也要顽强</h2><p>坐了二十分钟的车后我们下了车，距离结束还有一个多小时一点的时间，队长建议把 S16 毒绿，这样可以在最后一个测量点之前全部毒蓝。但我们发现 S16 上面还挂着碎片，决定驻守 S16，事实上这是鹰队在整场 XMA 唯一的贡献。S16 在下一个测量点变成了 research portal，蓝队成功进了几个球。在这期间还要和 GPS 对抗，实在不容易。但纵观全场，我们队里没有划分角色，大家打 portal 都是各打各的，如果组织再好一些，有人放炸、有人放盾、有人放脚，最后的战斗力会更强一些吧？</p><p>在最后一个测量点到来之前，我还离队占了一个更远处的 po，一时手抖 A 盾上成了 VRSB，截图留念，战斗到最后一刻，8 脚全都用光了。</p><p><img src="/img/IMG_6840.jpg" alt="VRSB"></p><h2 id="After-Party"><a href="#After-Party" class="headerlink" title="After Party"></a>After Party</h2><p>在 After Party 中，最惊喜一刻莫过于 Hank 宣布抵抗军取得了 XMA 的胜利，从而取得 Oasis 系列赛的最终胜利，毕竟在地面战和碎片战中一直被绿军碾压。</p><p><img src="/img/IMG_6867.jpg" alt="Resistance Win The Oasis"></p><p>回想自己年初才入坑（成为 Ingress 玩家），承蒙深蓝社团帮助，稀里糊涂参加了马尼拉奎松市的充电房，拿到了 Darsana Prime 的贡献章，加上今天的 Abaddon，算是参与了两个赛季。</p><h2 id="鸣谢"><a href="#鸣谢" class="headerlink" title="鸣谢"></a>鸣谢</h2><ul><li>感谢 @BGDajiu 开放的 Wi-Fi 热点</li><li>感谢 @Ma1aKai 赠予的 70 个 VR 盾和 88 个 AXA 盾</li><li>感谢各位读者读到这里，后会有期</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;img src=&quot;/img/abaddon_registration.png&quot; alt=&quot;Abaddon Prime&quot;&gt;&lt;/p&gt;
&lt;!--- more ---&gt;
&lt;h2 id=&quot;现场签到&quot;&gt;&lt;a href=&quot;#现场签到&quot; class=&quot;headerlink&quot; title=</summary>
      
    
    
    
    
    <category term="Ingress" scheme="https://jhuang.me/tags/Ingress/"/>
    
  </entry>
  
  <entry>
    <title>流经杏仁核的信号回圈</title>
    <link href="https://jhuang.me/amygdaloid-stimulus-circuit"/>
    <id>https://jhuang.me/amygdaloid-stimulus-circuit</id>
    <published>2018-09-04T14:49:47.497Z</published>
    <updated>2018-10-07T13:07:44.498Z</updated>
    
    <content type="html"><![CDATA[<p>又来到 leetcode，刚做完 Zigzag 转换的我觉得不能把时间浪费在这么没有意义的题目上。沿着算法这个标签页往下读，我看到第 37 题：Sudoku Solver，难度是困难，接受率 33%。</p><p>莫名其妙住进 522 房间的一号上铺以后，我买来下学期的化学教辅，一边配平化学方程式扫描任何没见过的分子式，一边在大脑里随机组合此起彼伏的名词与动词。十一月以后，大陆风渐渐盛行，干燥的天气让太阳光变得平易近人。诺大的操场跑着我丝毫不感兴趣的校运会，但是也因此有机会，能在太阳下，低着头听铅笔写在《数独 5》上喳喳的声音。</p><p>首先想到的是维护一个 9×9×9 的候选组，前两维是位置，后面是每个方格能填哪些候选值。第一次先扫描整个数独，同行、同列、同方块这个三个约束可以让候选组缩小，如果发现了哪个候选组长度是1，那就说明已经通过排除法确定这个元素是什么。这个时候填入这个元素，同时更新与这个元素同列、同行、同方块的候选值，然后接着找长度是 1 的候选组。这样就又回到之前的状态。</p><p>横竖的约束就像中国象棋里的車，每一个数字都能把同行同列杀得片甲不留，而那团团的九宫格里空位有时候本就不多，几趟車开下来，躲起来的数字就没地方跑了。新的数字又是新的車，奔向新的远方。侧过头看看X君，淡绿色制服，短发及肩。广播里单曲循环着已经是第47个年头的《运动员进行曲》，又被体育教头的「请某某、某某某同学到主席台前面……」打断，再被裁判的「预备……跑！」打断，再被班主任的「大家不要光在这里坐着……」打断，直到心里说：「这个地方要填3。」</p><p>候选组的思路和从前我做数独很相似，但我也知道有些标记成困难的题目实在找不到只有一个候选值的情况，这个时候就只能挨个去试验，也是非常苦恼且容易出错的一过程。回到刚才的算法，我们并不能断言每次总能运气这么好找到长度为1的候选组，所以需要教会计算机，或者是教会自己，如果最短候选组的长度大于1，应该怎么做。</p><p>应该怎么做呢？每个用来填数字的方格有四个顶点，于是我可以用铅笔在方格的四个角落里用八号字体的蝇头小楷注上候选值，然后照着假设接着去做就好了。实在运气不好要推翻一堆数字的时候，那就拿个简单难度的数独来撒气吧。话是这么说，我看到只做了一半的数独，心中总是充满了挫败感。</p><p>可是编程就是不停地和自己的挫败感打架。你可以想象在 Visual Studio 2008 里面给一个几百行的 <code>main</code> 函数设置断点一步一步点击跳转监控整个状态集么？这个没有类型、没有内存安全的语言让调试过程充满了各种各样的惊喜。从来没有测试驱动的理念，大学里的编程教育也和现实工程脱节太远，于是验证算法的正确性的唯一方式就变成了人类本能——看 log。在晚上十一点半以后，我终于把笨拙的加法计算器写出来。程序输出结果的那一刻，我没有想到十年后的自己在书写着数独求解器的测试用例。既然对候选组长度大于1的情形没有思路，那不如先把测试写了吧。</p><p>偶尔翻到一页有X君的笔迹，心中的一秒钟欢喜给下一分钟的波澜不惊做好了铺垫，还是不叨扰她的未完成吧。快到中学的时候爸妈给我买了一本三十九块钱的接力出版社出版的《彩图袖珍自然百科》，全彩铜版纸印刷的三十二开本，讽刺的是每天睡觉前我都喜欢把书拿到床头，然后就这么放在边桌上，看书页边缘溢出来的一点点彩色油墨，每16页叠成一个书帖，30多个书帖沿着锁线的方向起伏，这些头发丝里的般点也竟铺成了一窗夜空。我惊讶于这本书的精致，尽管只是普通的精装书而已，而只愿我永不打开这书，也就永远能看到它最完美的样子了。也正如彼时一样，这一页未完成也因X君而带上了无瑕，如果要让它停在最完美的时刻，最好的便是逃跑一般地翻到下一页。</p><p>撇开实现看测试，书写测试本身是直接的。原问题使用<code>[][]byte</code>来建模一个数独，每个 byte 取<code>1</code>到<code>9</code>这九个 ASCII 字符作为数字1到9，取<code>.</code>作为空白的方格。<code>SolveSudoku</code>本身修改这个二维切片引用的数据，修改完后再取这个切片把数据打印出来就可以了。测试数据为了人类输入方便，是一个换行分割的字符串，最终打印出来的也是这样的格式。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ExampleSolveSudoku</span><span class="params">()</span></span> &#123;</span><br><span class="line">sudoku :=</span><br><span class="line"><span class="string">`</span></span><br><span class="line"><span class="string">53..7....</span></span><br><span class="line"><span class="string">6..195...</span></span><br><span class="line"><span class="string">.98....6.</span></span><br><span class="line"><span class="string">8...6...3</span></span><br><span class="line"><span class="string">4..8.3..1</span></span><br><span class="line"><span class="string">7...2...6</span></span><br><span class="line"><span class="string">.6....28.</span></span><br><span class="line"><span class="string">...419..5</span></span><br><span class="line"><span class="string">....8..79</span></span><br><span class="line"><span class="string">`</span></span><br><span class="line">board := stringToBoard(sudoku)</span><br><span class="line">SolveSudoku(board)</span><br><span class="line">result := BoardToString(board)</span><br><span class="line">fmt.Println(result)</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// 534678912</span></span><br><span class="line"><span class="comment">// 672195348</span></span><br><span class="line"><span class="comment">// 198342567</span></span><br><span class="line"><span class="comment">// 859761423</span></span><br><span class="line"><span class="comment">// 426853791</span></span><br><span class="line"><span class="comment">// 713924856</span></span><br><span class="line"><span class="comment">// 961537284</span></span><br><span class="line"><span class="comment">// 287419635</span></span><br><span class="line"><span class="comment">// 345286179</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不管是横竖的约束，还是九宫格的约束，人做数独的喜悦都在于搜索所有条件并寄希望于能靠运气碰到一个靠约束可以唯一确定的空格。明白这个问题本质上是在潜在解空间中寻找一个解，那么对计算机来说，它不需要像人类这么左顾右盼找运气，它只需要踏踏实实从第一个空开始尝试就可以了。寻找到第一个空格的地方，从<code>1</code>试到<code>9</code>，如果没有冲突，再继续尝试下一个数字。它不必焦虑于从<code>1</code>尝试到<code>9</code>是不是显得很蠢。这个空格左边就是一个<code>1</code>，怎么可能从<code>1</code>开始试呢？但本质而言，填了<code>1</code>被左边的<code>1</code>否决掉，和填了<code>2</code>，<code>9</code>，<code>3</code> 才发现第 3 个空格里的 <code>3</code> 不满足约束，从而必须尝试下一个 <code>294</code> 的解这两件事情对计算机来说毫无区别，如果第一个<code>1</code>就被否决掉，那么就从下一个解开始继续试。<code>1</code>的下一个是<code>2</code>， <code>293</code>的下一个是 <code>294</code>，无论长短，无论是填了一个就被否掉，抑或是填完了快一半的空格才沮丧地发现要推倒重来，计算机没有情绪起伏，它不因「似乎可以直接否定某个候选值」而开心，也不因「为什么做了一半才发现整个假设从头开始就错了」而难过。它简单而愚蠢，但这个方法却因为它可以在初期就排除掉很多不可能的解而在实际运算中经常非常高效。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">bt</span><span class="params">(solution PartialSolution)</span> *<span class="title">PartialSolution</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> reject(solution) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> accept(solution) &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;solution</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> s := first(solution); s != <span class="literal">nil</span>; &#123;</span><br><span class="line">result := bt(*s)</span><br><span class="line"><span class="keyword">if</span> result != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br><span class="line">s = next(*s)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>剩下就只需要按照数独的规则实现 <code>reject</code>, <code>accept</code>, <code>first</code>, <code>next</code> 这几个函数就可以了。</p><p>印象中<code>bt</code>更多是变态的拼音首字母缩写和 BitTorrent 的大写字母缩写。「变态」这个词一方面来源于对生物学上的 metamorphosis 的翻译，另一方面却又因为日本人率先把 Sexual Perversion 翻译成「変態性欲」而又传回中国被大家意指「怪胎」或是「色狼」。一个词带上了色情意味就总会让含蓄的中国人想办法去避讳，避讳的结果就是新创出行话以显示自己在社会活得长了，也是见过世面的。于是从「傻屄」到「傻逼」到「傻B」到「sb」，侮辱意味的名词不断地吞噬同音字、拼音缩写词，最后以至于「jb」的原字「𣬠𣬶」竟然只能放在 CJK 表意文字扩展 B 区了，而近于无人知晓了。也罢，这些奇怪的□□不都是叫做「乱码」了。有了这顶帽子，也就不必追寻本来的意味了。</p><p>这个时候已经有了计算机批量生成的数独，编者要在前言中注明这些数独都是人精心构造出来的。与机器生成的不同，这些数独会有着独特的美感，例如某几个数字的对称性，数独的提示只有固定的两三个数字，其实就是计算机算出来的数独再加上一些审美的价值判断。但多巴胺总会激励你继续往前做，翻过一座又一座的山，直到频次降低，漫长而不经意地把这个习惯丢失。但人不甘心从有到无的过程变化得那么缓慢，而总愿意安排一个模糊而又实质的时间——「在那一刻，我不再做数独了。」——显得改变总是一瞬间，千丝万缕，斩不断理还乱，而不愿明说这一刻的 Unix 纪元。</p><p>只要十一点过后是九点半，地球又往西摆动了 22.5°，二年级的体育生又一次打破了年级记录，运动员进行曲又提前了80秒，《数独 5》又往前翻八页（不想说还有六页是X君的未完成），X君始终低着头，看不清楚颜面。那么永恒，就是这样一个回圈。</p><p>而我不愿意就这样草草收场。如果说 <code>for &#123; &#125;</code> 就是一个最简单的回环，什么事情也不做，来回地困在当下。我更愿意回圈是一个生成器，一个取票机的工厂：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">fibonacci</span>(<span class="params">current = BigInt(<span class="number">0</span>), next = BigInt(<span class="number">1</span>)</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">yield</span> current;</span><br><span class="line">  <span class="keyword">yield</span> *fibonacci(next, current + next);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> ticket = fibonacci();</span><br><span class="line">ticket.next();</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>每次都能通过<code>ticket.next(ticket.value)</code>取出一张新的船票。永远在沉睡，永远不结束，永远在等待下一次计算；但没有不确定性，没有噪音，没有抬头，没有句号。</p><p>110 纳秒以后，它输出了这个数独的解。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;又来到 leetcode，刚做完 Zigzag 转换的我觉得不能把时间浪费在这么没有意义的题目上。沿着算法这个标签页往下读，我看到第 37 题：Sudoku Solver，难度是困难，接受率 33%。&lt;/p&gt;
&lt;p&gt;莫名其妙住进 522 房间的一号上铺以后，我买来下学期的化</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>JavaScript 正则表达式匹配汉字</title>
    <link href="https://jhuang.me/2018/01/26/JavaScript-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D%E6%B1%89%E5%AD%97/"/>
    <id>https://jhuang.me/2018/01/26/JavaScript-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%8C%B9%E9%85%8D%E6%B1%89%E5%AD%97/</id>
    <published>2018-01-26T16:00:55.867Z</published>
    <updated>2019-10-27T03:17:47.066Z</updated>
    
    <content type="html"><![CDATA[<figure><image alt="供氧口藏文、汉字、拉丁字母" class="lozad" data-src="/img/IMG_0376.jpg"></image><figcaption>使用了藏文、汉字、拉丁字母三种文字的「供氧口」</figcaption></figure><h2 id="一个可能有-20-年历史的正则表达式"><a href="#一个可能有-20-年历史的正则表达式" class="headerlink" title="一个可能有 20 年历史的正则表达式"></a>一个可能有 20 年历史的正则表达式</h2><p>在谷歌搜索「JavaScript 正则表达式匹配汉字」的时候，前几条结果全都是<code>/[\u4e00-\u9fa5]/</code>。没有人怀疑这个正则表达式有什么问题，那么在 2018 年的今天，让我们站在 Chrome 64 的肩膀上，放飞一下自我。</p><a id="more"></a><p>汉文（Han Script）是汉语、日本语、朝鲜语、韩国语的书写系统中的一种文字（Script），越南语在早期也曾在书写系统中使用汉文<sup><a href="#ref1" name="fnref1">1</a></sup>。汉字（CJK Ideograph）是汉文的基本单元。汉字文化圈中的许多国家或地区都对汉字提出了自己的编码标准，Unicode 将这些标准加总在一起进行统一编码，力求实现原标准与 Unicode 编码之间的无损转换。Unicode 从语义（semantic）、抽象字形（abstract shape），具体字形（typeface）三个维度<sup><a href="#ref2" name="fnref2">2</a></sup>出发，把不同编码标准里「起源相同、本义相同、形状一样或稍异」的汉字赋予相同编码，这些被编码的字符称为中日韩统一表意文字（下文我们提到的「汉字」，如果不加说明，均指代中日韩统一表意文字）。如果把它们全部列举出来写成正则表达式，那么就是技术上完整的匹配汉字的正则表达式了。</p><p>正则表达式<code>/[\u4e00-\u9fa5]/</code>的意思是匹配所有从 U+4E00, <span style="font-variant:small-caps">cjk unified ideograph-4e00</span> 到 U+9FA5, <span style="font-variant:small-caps">cjk unified ideograph-9fa5</span> 的字符。这一段区域对应的是 Unicode 1.0.1 就收录进来的中日韩统一表意文字（CJK Unified Ideographs）区块，在 Unicode 3.0 加入表意文字扩展 A 区块以前，这个正则表达式确实给出了所有汉字的编码。换言之，从1992年到1999年，这个正则表达式确实是正确的。这么看来，想必这个表达式已经有20年历史了。</p><h2 id="匹配所有统一表意文字"><a href="#匹配所有统一表意文字" class="headerlink" title="匹配所有统一表意文字"></a>匹配所有统一表意文字</h2><p>然而时光飞逝，Unicode 在2017年6月发布了10.0.0版本。在这20年间，Unicode 添加了许多汉字。比如 Unicode 8.0 添加的 109 号化学元素名称「鿏（⿰⻐麦）」，其码点是 9FCF，就不在这个正则表达式范围中。如果我们期望程序里的<code>/[\u4e00-\u9fa5]/</code>可以与时俱进匹配最新的 Unicode 标准，显然是不现实的事情。因此，我们需要换一个思路，写一个无需维护的正则表达式：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/\p&#123;Unified_Ideograph&#125;/u</span><br></pre></td></tr></table></figure><p>其中<code>\u</code>是 ECMAScript 2015 定义的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp">正则表达式标志</a>，意味着将表达式作为 Unicode 码点序列。<code>\p&#123;&#125;</code>是 ECMAScript 2018 定义的<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes">正则表达式 Unicode 属性转义</a>，它向开发者提供了根据 Unicode 字符的属性数据<sup><a href="#ref3" name="fnref3">3</a></sup>构造正则表达式的能力。<a href="https://www.unicode.org/reports/tr44/#Unified_Ideograph"><code>Unified_Ideograph</code></a>是 Unicode<br> 字符的一个二值属性，对于汉字，其取值为 Yes，否则为 No。因此<code>\p&#123;Unified_Ideograph&#125;</code>匹配所有满足<code>Unified_Ideograph=yes</code>的 Unicode 字符，而它的底层实现由运行时所依赖的 Unicode 版本决定，开发者不需要知道汉字的具体 Unicode 码点范围。</p><h2 id="容易混淆的其他-Unicode-属性转义表达式"><a href="#容易混淆的其他-Unicode-属性转义表达式" class="headerlink" title="容易混淆的其他 Unicode 属性转义表达式"></a>容易混淆的其他 Unicode 属性转义表达式</h2><h3 id="p-Ideographic-u"><a href="#p-Ideographic-u" class="headerlink" title="/\p{Ideographic}/u"></a><code>/\p&#123;Ideographic&#125;/u</code></h3><p>这个表达式匹配所有满足<code>Ideographic=yes</code>的 Unicode 字符。它是<code>Unified_Ideograph=yes</code>的超集。我们先看一下 UAX #44 对这个属性的解释<sup><a href="#ref4" name="fnref4">4</a></sup>：</p><blockquote><p>Characters considered to be CJKV (Chinese, Japanese, Korean, and Vietnamese) or other siniform (Chinese writing-related) ideographs. This property roughly defines the class of “Chinese characters” and does not include characters of other logographic scripts such as Cuneiform or Egyptian Hieroglyphs.</p></blockquote><p>这个属性表明该字符属于 CJKV 表意文字或者与汉语书写相关的其他表意文字（如西夏文、女书），这个属性粗略地定义了「中文字符」的分类。我们查看<a href="http://www.unicode.org/Public/10.0.0/ucd/PropList.txt">Unicode 10.0.0 字符属性列表</a>可以知道，在 Unicode 10.0.0 中，</p><details>    <summary>Ideographic 属性为 yes 的字符</summary><pre>3006          ; Ideographic # Lo       IDEOGRAPHIC CLOSING MARK3007          ; Ideographic # Nl       IDEOGRAPHIC NUMBER ZERO3021..3029    ; Ideographic # Nl   [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE3038..303A    ; Ideographic # Nl   [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY3400..4DB5    ; Ideographic # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB54E00..9FEA    ; Ideographic # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEAF900..FA6D    ; Ideographic # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6DFA70..FAD9    ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD917000..187EC  ; Ideographic # Lo [6125] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187EC18800..18AF2  ; Ideographic # Lo [755] TANGUT COMPONENT-001..TANGUT COMPONENT-7551B170..1B2FB  ; Ideographic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB20000..2A6D6  ; Ideographic # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D62A700..2B734  ; Ideographic # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B7342B740..2B81D  ; Ideographic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D2B820..2CEA1  ; Ideographic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA12CEB0..2EBE0  ; Ideographic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE02F800..2FA1D  ; Ideographic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D&#35; Total code points: 96174</pre></details><p>囊括了所有统一表意文字、西夏文及其组件、女书、中日韩兼容性字符、苏州码子、「〇」以及日本语中的书信结尾标志「〆」。使用<code>/\p&#123;Ideographic&#125;/u</code>来匹配汉字会过于宽泛。一是包含了西夏文、女书，二是只用于编码转换用的兼容字符也纳入其中。</p><h3 id="p-Script-Han-u"><a href="#p-Script-Han-u" class="headerlink" title="/\p{Script=Han}/u"></a><code>/\p&#123;Script=Han&#125;/u</code></h3><p><code>Script</code> 属性<sup><a href="#ref5" name="fnref5">5</a></sup>用来筛选满足下面条件的一组字符：</p><ol><li>字符的书写形式具有共同的图像特征与文字流变</li><li>该组字符全部用来表达某个书写系统内的文本信息（textual information）</li></ol><p>我们查看<a href="http://www.unicode.org/Public/10.0.0/ucd/Scripts.txt">Unicode 10.0.0 Scripts</a>可以知道，</p><details>    <summary>满足Script=Han的字符</summary><pre>2E80..2E99    ; Han # So  [26] CJK RADICAL REPEAT..CJK RADICAL RAP2E9B..2EF3    ; Han # So  [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE2F00..2FD5    ; Han # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE3005          ; Han # Lm       IDEOGRAPHIC ITERATION MARK3007          ; Han # Nl       IDEOGRAPHIC NUMBER ZERO3021..3029    ; Han # Nl   [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE3038..303A    ; Han # Nl   [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY303B          ; Han # Lm       VERTICAL IDEOGRAPHIC ITERATION MARK3400..4DB5    ; Han # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB54E00..9FEA    ; Han # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEAF900..FA6D    ; Han # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6DFA70..FAD9    ; Han # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD920000..2A6D6  ; Han # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D62A700..2B734  ; Han # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B7342B740..2B81D  ; Han # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D2B820..2CEA1  ; Han # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA12CEB0..2EBE0  ; Han # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE02F800..2FA1D  ; Han # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D<p>&#35; Total code points: 89228</pre></details></p><p>囊括了所有统一表意文字、中日韩兼容性字符、苏州码子、「〇」、「〆」、「々」以及字典常用的部首。从前面汉文（Han Script）与汉字（CJK Ideograph）的关系我们可以知道，<code>/\p&#123;Script=Han&#125;/u</code>匹配的是汉文作为一个字符集里面的所有字符，因此它包括了部首、「々」等字符，这些字符要么当它们独立存在的时候没有语言意义（部首独立存在是一个符号），要么无法独立存在（「々」依赖于所修饰的汉字）。所以汉字是汉文的一个单元，汉文除了包含汉字以外，还包括这些部首符号、数字、修饰符。因此使用<code>/\p&#123;Script=Han&#125;/u</code>来匹配汉字混淆了汉文与汉字的概念范围。</p><h2 id="浏览器兼容性支持"><a href="#浏览器兼容性支持" class="headerlink" title="浏览器兼容性支持"></a>浏览器兼容性支持</h2><h3 id="JavaScript"><a href="#JavaScript" class="headerlink" title="JavaScript"></a>JavaScript</h3><p>截至2019年10月，Chrome 64 以上, Safari 11.1 以上都<a href="https://caniuse.com/#feat=mdn-javascript_builtins_regexp_property_escapes">支持</a> 正则表达式 Unicode 属性转义。对于其他浏览器，我们推荐使用 7.7 版本的<code>@babel/env</code>转译配置将带有属性转义的正则表达式转为 Unicode 码点正则表达式。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;presets&quot;</span>: [<span class="string">&quot;@babel/env&quot;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>就可以根据<code>browserslist</code>指定的浏览器版本自动决定是否进行此特性的转译。注意 Babel 7.3 - 7.6 的版本对此特性支持产生了<a href="https://github.com/babel/babel/issues/10367">问题</a>，该问题已经在<a href="https://github.com/babel/babel/pull/10447">PR 10447</a>得到解决，将随 Babel 7.7.0 一同发布。</p><p>如果您还在使用更老一点的 Babel 7.x 版本，需要用<code>babel</code>转译插件<a href="https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-unicode-property-regex">@babel/plugin-proposal-unicode-property-regex</a>。<code>@babel/env</code> 背后也使用了这个插件。</p><p>上述转译结果的在线演示可以在<a href="https://mothereff.in/regexpu#input=const+regex+%3D+/%5Cp%7BUnified_Ideograph%7D/u%3B&unicodePropertyEscape=1">这里</a>查看，开发者可以自己在上面转译其他的 Unicode 属性转义正则表达式。我们在这里列举<code>/\p&#123;Unified_Ideograph&#125;/u</code>转译成Unicode 码点正则表达式的结果：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/\p&#123;Unified_Ideograph&#125;/u</span>;</span><br><span class="line"><span class="comment">// transpiled to ES6:</span></span><br><span class="line"><span class="keyword">const</span> regex = <span class="regexp">/[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29\u&#123;20000&#125;-\u&#123;2A6D6&#125;\u&#123;2A700&#125;-\u&#123;2B734&#125;\u&#123;2B740&#125;-\u&#123;2B81D&#125;\u&#123;2B820&#125;-\u&#123;2CEA1&#125;\u&#123;2CEB0&#125;-\u&#123;2EBE0&#125;]/u</span>;</span><br></pre></td></tr></table></figure><p>从上面这个正则表达式可以知道，转译的结果跟 </p><details>    <summary>Unicode 10.0.0 中 Unified_Ideograph 属性为 yes 的字符</summary><pre>3400..4DB5    ; Unified_Ideograph # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB54E00..9FEA    ; Unified_Ideograph # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEAFA0E..FA0F    ; Unified_Ideograph # Lo   [2] CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0FFA11          ; Unified_Ideograph # Lo       CJK COMPATIBILITY IDEOGRAPH-FA11FA13..FA14    ; Unified_Ideograph # Lo   [2] CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14FA1F          ; Unified_Ideograph # Lo       CJK COMPATIBILITY IDEOGRAPH-FA1FFA21          ; Unified_Ideograph # Lo       CJK COMPATIBILITY IDEOGRAPH-FA21FA23..FA24    ; Unified_Ideograph # Lo   [2] CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24FA27..FA29    ; Unified_Ideograph # Lo   [3] CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA2920000..2A6D6  ; Unified_Ideograph # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D62A700..2B734  ; Unified_Ideograph # Lo [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B7342B740..2B81D  ; Unified_Ideograph # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D2B820..2CEA1  ; Unified_Ideograph # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA12CEB0..2EBE0  ; Unified_Ideograph # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0&#35; Total code points: 87882</pre></details>严格对应。因此转译是正确的。<p>使用</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;plugins&quot;</span>: [</span><br><span class="line">    [<span class="string">&quot;@babel/plugin-proposal-unicode-property-regex&quot;</span>, &#123; <span class="string">&quot;useUnicodeFlag&quot;</span>: <span class="literal">false</span> &#125;]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置将表达式转成 ES5 支持的以字符的 UTF16 表示为序列的字符串，这里不再赘述。</p><h3 id="input-元素的-pattern-属性"><a href="#input-元素的-pattern-属性" class="headerlink" title="input 元素的 pattern 属性"></a><code>input</code> 元素的 <code>pattern</code> 属性</h3><p>在前端技术中，除了JavaScript会用到正则表达式，HTML 里<code>&lt;input&gt;</code>元素的<code>pattern</code>属性也会用到正则表达式。与 JavaScript 相比，<code>pattern</code>不支持设置正则表达式的标志位，因此 HTML 标准中强制规定了 <code>input</code> 元素的 <code>pattern</code> 属性需要施加<code>unicode</code>标志<sup><a href="#ref6" name="fnref6">6</a></sup>。目前除了 IE 和 Edge 18 以外的主流浏览器都已经<a href="https://mathiasbynens.be/notes/es6-unicode-regex#support">实现</a>了这一标准。</p><p>在 React/Angular/Vue.js 三大前端框架中，Angular 提供了近似于 <code>pattern</code> 的指令 <code>ngPattern</code>。目前<code>ngPattern</code>尚未施加<code>unicode</code>标志<sup><a href="#ref7" name="fnref7">7</a></sup>。AngularJS 的 <code>ngPattern</code> directive 仍未施加。</p><p>在大部分情况，是否施加<code>unicode</code>标志不会对正则表达式产生语义区别。主要的差别在于：</p><ul><li>在使用<code>\u&#123;10000&#125;</code>表示 Unicode 码点字符情形，正则表达式<code>/\u&#123;10000&#125;/</code>代表匹配<code>u</code>一万次，<code>/\u&#123;10000&#125;/u</code>匹配字符<code>\u&#123;10000&#125;</code>一次</li><li><code>/./</code>只匹配 BMP 平面的字符，<code>/./u</code>匹配所有平面的字符。</li></ul><p>由于 Unicode 属性转义正则表达式需要 Chrome 64 以上，Safari 11.1 以上，因此下面的用法目前只能在这些浏览器中使用：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">pattern</span>=<span class="string">&quot;\p&#123;Unified_Ideograph&#125;&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>因此，如果需要兼容其他浏览器，可以使用转译插件的底层库<a href="https://github.com/mathiasbynens/regexpu-core">regexpu-core</a>在 JavaScript 中转换正则表达式，再把转换结果通过 HTML 模版输送过去中，但无疑这是一个很麻烦的做法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> rewritePattern = <span class="built_in">require</span>(<span class="string">&quot;regexpu-core&quot;</span>);</span><br><span class="line">rewritePattern(<span class="string">&#x27;\\p&#123;Unified_Ideograph&#125;&#x27;</span>, <span class="string">&#x27;u&#x27;</span>, &#123;</span><br><span class="line">  <span class="string">&#x27;unicodePropertyEscape&#x27;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&#x27;useUnicodeFlag&#x27;</span>: <span class="literal">false</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// → &#x27;/(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])/&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li><code>/[\u4e00-\u9fa5]/</code>已经过时了，请不要再使用二十年前的正则表达式了。</li><li><code>/\p&#123;Unified_Ideograph&#125;/u</code>不需要维护，匹配所有汉字。这里<code>\p</code>是 Unicode 属性转义正则表达式。</li><li><code>/\p&#123;Ideographic&#125;/u</code> 和 <code>/\p&#123;Script=Han&#125;/u</code> 都是<code>/\p&#123;Unified_Ideograph&#125;/u</code>的超集，匹配了除了汉字以外的其他一些字符，在「汉字匹配正则表达式」这个需求下，是错的。</li><li>目前 Chrome 和 Safari 支持 Unicode 属性转义正则表达式。对其他环境，使用 7.7 版本的 <code>@babel/env</code> 就可以自动根据浏览器规定打开支持。</li></ol><small><a name="ref1">1</a>: <a href="http://www.unicode.org/versions/Unicode10.0.0/ch06.pdf" target="_blank" rel="noopener">Unicode 10.0.0 第六章第一节，书写系统</a> <a href="#fnref1">↩</a><br><a name="ref2">2</a>: <a href="http://www.unicode.org/versions/Unicode10.0.0/ch18.pdf" target="_blank" rel="noopener">Unicode 10.0.0 第十八章第一节，东亚</a> <a href="#fnref2">↩</a><br><a name="ref3">3</a>: <a href="http://www.unicode.org/Public/10.0.0/ucd/PropList.txt" target="_blank" rel="noopener">Unicode 10.0.0 字符属性列表</a> <a href="#fnref3">↩</a><br><a name="ref4">4</a>: <a href="http://www.unicode.org/reports/tr44/tr44-20.html#Property_Definitions" target="_blank" rel="noopener">UAX #44 第 20 版的属性说明</a> <a href="#fnref4">↩</a><br><a name="ref5">5</a>: <a href="http://www.unicode.org/reports/tr24/tr24-27.html#Introduction" target="_blank" rel="noopener">UAX #24 第 27 版</a> <a href="#fnref5">↩</a><br><a name="ref6">6</a>: <a href="https://html.spec.whatwg.org/multipage/input.html#the-pattern-attribute" target="_blank" rel="noopener">HTML 标准中<code>input</code>元素的<code>pattern</code>属性</a> <a href="#fnref6">↩</a><br><a name="ref7">7</a>: <a href="https://github.com/angular/angular/pull/20819" target="_blank" rel="noopener">给<code>ngPattern</code>施加<code>unicode</code>标志</a> <a href="#fnref7">↩</a></small>]]></content>
    
    
    <summary type="html">&lt;figure&gt;
&lt;image alt=&quot;供氧口藏文、汉字、拉丁字母&quot; class=&quot;lozad&quot; data-src=&quot;/img/IMG_0376.jpg&quot;&gt;&lt;/image&gt;
&lt;figcaption&gt;使用了藏文、汉字、拉丁字母三种文字的「供氧口」&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;一个可能有-20-年历史的正则表达式&quot;&gt;&lt;a href=&quot;#一个可能有-20-年历史的正则表达式&quot; class=&quot;headerlink&quot; title=&quot;一个可能有 20 年历史的正则表达式&quot;&gt;&lt;/a&gt;一个可能有 20 年历史的正则表达式&lt;/h2&gt;&lt;p&gt;在谷歌搜索「JavaScript 正则表达式匹配汉字」的时候，前几条结果全都是&lt;code&gt;/[\u4e00-\u9fa5]/&lt;/code&gt;。没有人怀疑这个正则表达式有什么问题，那么在 2018 年的今天，让我们站在 Chrome 64 的肩膀上，放飞一下自我。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Unicode" scheme="https://jhuang.me/tags/Unicode/"/>
    
  </entry>
  
  <entry>
    <title>2017年终</title>
    <link href="https://jhuang.me/2017/12/31/2017%E5%B9%B4%E7%BB%88/"/>
    <id>https://jhuang.me/2017/12/31/2017%E5%B9%B4%E7%BB%88/</id>
    <published>2018-01-01T01:20:12.000Z</published>
    <updated>2018-01-26T15:04:42.299Z</updated>
    
    <content type="html"><![CDATA[<p><image alt="准备安装所有 Noto Sans 字体" class="lozad" data-src="/img/IMG_0713.jpg"></image></p><p>一座温暖的南方城市，新的开始。</p><a id="more"></a><p>我庆幸可以在年终看自己做过的事情觉得幼稚而可爱，解决的问题那么简陋却又自诩折腾精神。这毕竟也意味着，这两年没有白过。</p><p>渐渐发现最痛快事还是精研标准，名正言顺。慢慢脱离了个人博客等信息源，可以一眼看出信息源的段位和水平；慢慢觉得知乎许多内容都是媒体创作，因而越来越挑剔。</p><p>今年在 GitHub 有 <a href="https://github.com/JLHwung?tab=overview&from=2017-12-01&to=2017-12-31">801 个贡献</a>，去年是 275 个，还有许多是业务代码。这是去年没有想象到的。给 node.js、angular、moment.js 都贡献了代码，也是去年没有想象到的。</p><p>要说生活上最重要的，我觉得是「取舍」二字。「取舍」不是说这里要多一些，那里要少一些；而是干脆利落，该「取」则要大力出奇迹，该「舍」则要一毛不拔。比如说买五件同样的衣服，买两斤同样的糖果；比如说不买什么。物事越少，留给自己支配的注意力就越多。</p><p>要说学习上最要紧事，便是明晰概念，建构体系。少年时代在大学课堂上，最记得老师说的便是「明晰概念」，到自己去学习 Unicode 的时候，便觉得概念清晰才能举一反三，触类旁通。「纸上得来终觉浅，绝知此事要躬行」说得就是它。</p><p>一段对自己冲击比较大的旅行确实受益匪浅。比如坐了53个小时的火车到拉萨，清晨的高原，深受婆罗米系文字影响的<a href="https://zh.wikipedia.org/wiki/%E8%97%8F%E6%96%87">藏文</a>，与汉语有<a href="https://zh.wikipedia.org/wiki/%E5%8E%9F%E5%A7%8B%E6%BC%A2%E8%97%8F%E8%AA%9E">同源关系</a>的藏语，寺庙上已经变成装饰意义的<a href="https://zh.wikipedia.org/zh-hant/%E8%98%AD%E6%9C%AD%E6%96%87">兰扎文</a>，无不款款向我展开了生活奇妙的小径。</p><p>许多软件开发工作必然会被人工智能所取代，趁现在，不如早日把人类的过去历史进行数字化，打通人机间的信息交互之门。越来越觉得时间不够用的时候，只能自省学习的过程，争取早日给 Unicode 做贡献。</p><p>最后，让我们永远为文明进步而热泪盈眶。</p><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><p>此篇附录与正文毫无关系，只是作者偷懒牵强贴上来的。</p><p>Fact: Two compatibily equivalent Unicode string does NOT have to share same length of grapheme clusters.</p><p>Example:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="string">&quot;กํา&quot;</span>; <span class="comment">// &lt;0e01, 0e4d, 0e32&gt;</span></span><br><span class="line"><span class="keyword">let</span> b = <span class="string">&quot;กำ&quot;</span>; <span class="comment">// &lt;0e01, 0e33&gt;</span></span><br><span class="line">a.decomposedStringWithCompatibilityMapping == b.decomposedStringWithCompatibilityMapping</span><br><span class="line"><span class="comment">// returns true</span></span><br><span class="line">a.<span class="built_in">count</span></span><br><span class="line"><span class="comment">// returns 1</span></span><br><span class="line">b.<span class="built_in">count</span></span><br><span class="line"><span class="comment">// returns 2</span></span><br></pre></td></tr></table></figure><p>所以默认的 <a href="http://www.unicode.org/reports/tr29/tr29-31.html#Grapheme_Cluster_Boundaries">grapheme cluster boundary</a> 算法才说</p><blockquote><p>do not have to cover edge cases that will not occur in practice.</p></blockquote><p>因此 maxlength/minlength 这种跟用户密切相关，却又使用 JavaScript 字符串长度来判断的 HTML 属性，注定<a href="https://github.com/whatwg/html/issues/1467">充满了混乱</a>。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;image alt=&quot;准备安装所有 Noto Sans 字体&quot; class=&quot;lozad&quot; data-src=&quot;/img/IMG_0713.jpg&quot;&gt;&lt;/image&gt;&lt;/p&gt;
&lt;p&gt;一座温暖的南方城市，新的开始。&lt;/p&gt;</summary>
    
    
    
    <category term="念想" scheme="https://jhuang.me/categories/%E5%BF%B5%E6%83%B3/"/>
    
    
  </entry>
  
  <entry>
    <title>GitFlow 分支模型下的全量格式化代码流程</title>
    <link href="https://jhuang.me/2017/11/21/GitFlow%E5%88%86%E6%94%AF%E6%A8%A1%E5%9E%8B%E4%B8%8B%E7%9A%84%E4%BB%A3%E7%A0%81%E5%85%A8%E9%87%8F%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%B5%81%E7%A8%8B/"/>
    <id>https://jhuang.me/2017/11/21/GitFlow%E5%88%86%E6%94%AF%E6%A8%A1%E5%9E%8B%E4%B8%8B%E7%9A%84%E4%BB%A3%E7%A0%81%E5%85%A8%E9%87%8F%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%B5%81%E7%A8%8B/</id>
    <published>2017-11-22T02:46:23.000Z</published>
    <updated>2017-12-09T15:45:43.432Z</updated>
    
    <content type="html"><![CDATA[<h2 id="全量格式化还是渐进格式化？"><a href="#全量格式化还是渐进格式化？" class="headerlink" title="全量格式化还是渐进格式化？"></a>全量格式化还是渐进格式化？</h2><p>对于一个历史悠久又没有执行强制代码规范的代码库，全量格式化看起来是一件风险不可控的事情：它产生了大量的难以评估的格式改动，也使得格式化以后的代码库运行<code>git blame</code>几乎都会定位到进行格式化的人（而非是这段逻辑的上一次有意义的修改者）。相比之下，理想的做法似乎应该是渐进格式化：对于新代码，使用强制的代码规范；对于老代码，除非它被修改，不然不进行强制代码规范。</p><p>真的是这样子吗？</p><p><strong>全量格式化风险并不随代码量变大而显著变大。</strong> 全量格式化往往会产生改变巨大的提交，代码改变得越多，看起来引入问题的风险就会越大。然而，对于代码格式器而言，代码格式器需要处理的是语言规范里面定义的有限几种语法结构的组合，绝大部分的业务逻辑代码都不会有像编译器的测试用例一般复杂的语法结构，对于代码格式器而言，只要代码库中的语法结构都被测试覆盖，改一百行代码和改十万行代码并没有本质区别。换言之，如果代码格式器出错，那么导致的代码错误一定很容易被查出来（因为同样的语法结构在系统各个角落都会用到）。只要十万行代码用到的语法结构组合不是一百行代码的语法结构组合的一千倍，那么全量格式化十万行代码引入的问题就不会是只格式化一百行代码的一百倍，因此全量格式化代码引入的风险关于代码量完全不成线性关系。</p><p><strong>一致的代码风格降低开发成本。</strong> 全量格式化立刻使得代码库形成一致的代码风格，项目的每一个开发者都会备受鼓舞，一致的风格让开发者的阅读流不必被异常的代码格式阻断，对代码块的视觉识别也更加迅速（你的大脑很快习得了<code>function</code>后面一个空格的位置一定是一组参数，你不必为多一个或者少一个空格而额外消耗认知能力），配合上代码提交阶段的自动格式化，开发者甚至可以在开发过程中完全不需理会代码格式——反正最后提交时都会被计算机誊抄一遍进入到代码库中。</p><p><strong>增量格式化容易扩大化合并时的代码冲突。</strong> 可是渐进代码格式化给我更多的安全感？在 GitFlow 的分支管理模式中，假设一个未被格式化的文件分别在特性分支和补丁分支有了改动，那么改动将会触发该文件的格式化，我们会在后面指出——当两个分支的同一个文件分别进行化后，合并时的冲突块大小将会被格式化放大（因为 git 并不理解哪些改动是格式化，哪些改动是修改业务逻辑），很快你将会淹没在无处不在的合并冲突之中，更糟糕的是，因为 git 不理解格式化和业务逻辑修改的区别，你需要仔细核对才能知道哪些改动压根没有修改业务逻辑，这无疑增大了合并的时候错漏的风险。</p><p>那么，不如毕其功于一役吧。</p><h2 id="如何全量格式化-GitFlow-分支模型的代码库？"><a href="#如何全量格式化-GitFlow-分支模型的代码库？" class="headerlink" title="如何全量格式化 GitFlow 分支模型的代码库？"></a>如何全量格式化 GitFlow 分支模型的代码库？</h2><p>下图一个典型的采用 GitFlow 分支模型的代码库历史图</p><pre>          *---* feature/foo         /*---*---*---* develop     \      *---*---* master       \   \        \   * hotfix/bar         \          *---* release/1.0.0       </pre><p>在 GitFlow 模型中，考察分支的生命周期，假设所有分支都是根据需求合理存在的，可以发现：</p><ol><li><p>所有的 release 分支都是历史分支，它们不会再汇入到 master 分支</p></li><li><p>所有的 hotfix 分支都将汇入 master 分支，hotfix 分支生命周期很短</p></li><li><p>每一次发小版本的时候，master 分支作为 hotfix 的汇入者，也会汇入到 develop 分支</p></li><li><p>每一次发大版本的时候，develop 分支会汇入到 master 分支，并从新的 master 分支拉出 release 分支</p></li><li><p>所有的 feature 分支都将汇入 develop 分支</p></li></ol><p>根据这个生命周期，我们可以按照以下策略对代码库进行格式化，下面我们简称这个策略为<strong>两步格式化</strong>：</p><p><strong>一，准备代码格式化配置。</strong>对所有活跃分支准备好代码格式器的配置，我们不推荐全局安装代码格式器，而是把代码格式器作为项目的开发依赖，这样历史版本可以用老版本的代码格式器，新代码可以用到新的代码格式器。你可以从开发分支拉出一个分支增加格式器配置，再遴选到各个活跃分支。只添加格式化配置对代码业务不会产生影响。</p><p><strong>二，挑选一个合适的时间点进行批量的格式化处理。</strong>我们建议是在刚发完小版本之后，此时 master 刚刚汇入 develop 分支。然后再将 develop 分支都汇入到开发中的 feature 分支。此时历史如图所示。</p><pre>          *---*---* feature/foo         /       /*---*---*---*---* develop     \         /      *---*---* master       \        *---* release/1.0.0       </pre><p>这个时候，这些分支的领先程度满足下面的关系：</p><blockquote><p>feature 分支 &gt; develop 分支 &gt; master 分支</p></blockquote><p>这里的<code>&gt;</code>是严格领先，也就是 feature 分支包含了 develop 分支的所有改动，develop 分支包含了 master 分支的所有改动。</p><p><strong>三，单独对历史分支格式化。</strong> 对所有的还在维护的 release 分支，进行全量格式化，通过测试后提交，在下面的历史图中，我们简单记 release/1.0.0 的全量格式化 commit 为 O。</p><p><strong>四，格式化 master 分支。</strong> 从 master 分支新开一个专门进行格式化的 hotfix 分支，进行全量格式化，测试无误后，汇入到 master 分支，记这个格式化的 commit 为 A，此时历史如图所示。</p><pre>          *---*---* feature/foo         /       /*---*---*---*---* develop     \         /      *---*---*---A master       \        *---*---O release/1.0.0       </pre><p><strong>五，合并 develop 分支，使用 ours 策略消解冲突。</strong> 将此时的 master 分支再次汇入到 develop 分支，如果有冲突，使用 develop 分支的文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout develop</span><br><span class="line">git merge -s ours master</span><br></pre></td></tr></table></figure><p>这里的<code>-s ours</code>指的是合并时的冲突消解策略。因为我们的分支满足上述的严格领先关系，develop 一定拥有所有 master 的改动，而 master 此时除了进行代码格式化，没有任何的业务逻辑改动（只要格式化工具不出错），因此冲突产生的原因一定不是业务逻辑产生的代码冲突，对于这些冲突，我们使用 develop 的文件一定不会影响代码业务；反过来，如果没有冲突，说明对这个文件进行格式化，大概率等同于将 master 分支的格式化改动合并过来的效果，因此，我们可以直接沿用这些格式化改动。记这个合并的 commit 为 M.</p><p><strong>六，再次全量格式化 develop分支。</strong> 我们使用了 develop 分支自己的文件来消解冲突，而 develop 自己的文件是没有格式化的，因此，在合并完成后，我们再次进行全量的代码格式化，记这个格式化的 commit 为 B，此时分支历史如图所示。</p><pre>          *---*---* feature/foo         /       /*---*---*---*---*---M---B develop     \         /   /      *---*---*---A master       \        *---*---O release/1.0.0       </pre><p>现在，develop 和 master 仍然保持着严格领先的特点。</p><p><strong>七，对于 feature 分支，重复上述步骤，将 develop 汇入 feature。</strong> 在下面的例子中，我们记 feature/foo 分支格式化的 commit 为 C，容易知道，格式化完成后，此时的分支历史如图所示。</p><pre>          *---*---*-------*---C feature/foo         /       /       /*---*---*---*---*---M---B develop     \         /   /      *---*---*---A master       \        *---*---O release/1.0.0       </pre><p>进行完这个流程后，我们仍旧保持着 feature 分支 &gt; develop 分支 &gt; master 分支的严格领先关系。</p><p>仔细考察此时的 develop 分支，我们发现 develop 分支的格式化实际上是两个 commit A 和 B 共同作用的效果。通过这种合并策略，我们相当于告诉 git，</p><blockquote><p>develop 分支的格式化 = master 分支的格式化 A + Δ(develop - master) 的格式化 B</p></blockquote><p>并且，所有格式化 A 导致的代码冲突已经在 M 得到消解，M 点的版本一定是可用的，而 develop 的代码符合规范又通过 commit B 得到了保证。</p><p>这样，当下一次发小版本时，master 汇入了新的 hotfix 并且再回流到 develop 时，我们一定无需处理格式化 A 导致的代码冲突，新的可能的冲突一定是 develop 新增的业务逻辑与 hotfix 改动之间的冲突——我们回到了格式化之前的冲突原因。</p><p>同时，当下一次发大版本时，由于 develop 总是领先于 master，并且 A 导致的代码冲突已经在 M 中得到了消解，我们也不会遇到合并冲突。</p><p>至此，我们完成了对代码库的全量格式化。</p><h2 id="为什么不能对每一个分支做格式化再合并？"><a href="#为什么不能对每一个分支做格式化再合并？" class="headerlink" title="为什么不能对每一个分支做格式化再合并？"></a>为什么不能对每一个分支做格式化再合并？</h2><p>对每一个分支进行格式化是最容易想到的全量格式化方式，但它会扩大冲突块的大小，因为 git 只会记录文件改动，它不知道哪一些是代码格式化的改动，哪一些是业务逻辑的改动，我们不妨看一段简单的代码，看看格式化后的冲突块大小。为了简单起见，考虑只有 master 和 develop 分支的情形，源代码只有一个叫做 <code>index.js</code> 的文件。你也可以在<a href="https://github.com/JLHwung/format-whole-codebase-playground">这个仓库</a>查看这个小项目的源代码与分支历史。此时历史如图所示。</p><pre>  C master     /    A---B develop  </pre><p>各个 commit 的 <code>index.js</code> 内容如下表显示</p><table><thead><tr><th>commit</th><th>内容</th></tr></thead><tbody><tr><td>A</td><td><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>)</span>&#123; <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> &#125;</span><br></pre></td></tr></table></figure></td></tr><tr><td>B</td><td><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>)</span>&#123; <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + <span class="string">&quot;bar&quot;</span> &#125;</span><br></pre></td></tr></table></figure></td></tr><tr><td>C</td><td><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> <span class="string">&quot;bar&quot;</span>; &#125;</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>)</span>&#123; <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + bar() &#125;</span><br></pre></td></tr></table></figure></td></tr></tbody></table><p>显然当 master 汇入到 develop 时，将会产生冲突，现在，我们分别格式化两个分支，并进行合并，此时<code>index.js</code>的冲突如图</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + bar();</span><br><span class="line">=======</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&gt;&gt;&gt;&gt;&gt;&gt;&gt; master</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>显然，因为格式化与业务逻辑的变更混在了一起，此时整个<code>index.js</code>都发生了冲突，仔细观察 <code>module.exports = function(foo) &#123;</code> 一行，显然这一行是格式化修改后的结果，业务逻辑修改与这一行无关，但由于分别格式化了原本就有冲突的分支，此时合并的时候格式化的改动也被视作冲突了（同时在不同的 commit 基础上把这一行改动成了<code>module.exports = function(foo) &#123;</code>），因此，冲突块扩大了。</p><h2 id="为什么不能合并完保持分支领先关系后，再对每一个分支分别格式化？"><a href="#为什么不能合并完保持分支领先关系后，再对每一个分支分别格式化？" class="headerlink" title="为什么不能合并完保持分支领先关系后，再对每一个分支分别格式化？"></a>为什么不能合并完保持分支领先关系后，再对每一个分支分别格式化？</h2><p>现在我们先将 master 合并到 develop 消解冲突，再分别进行格式化，此时历史如图。</p><pre>  ----C---E master     /     \A---B---D---F develop  </pre><p>其中F和E是格式化的 commit，现在我们把格式化后的 E 合并到 D，我们预期应该不会有业务逻辑产生的冲突，因为 B 和 C 的冲突在 D 中已经消解过了，然而，由于 E 和 F 都分别格式化了 C 和 D 中的内容，此时产生了单纯由格式化产生的冲突。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + bar();</span><br><span class="line">=======</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&gt;&gt;&gt;&gt;&gt;&gt;&gt; master</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这个冲突和上面居然是一样的。这是因为在 D 中我们可以单纯地使用 B 作为消解冲突的方式，最后的结果和分别格式化分支是一样的。尽管这是冲突消解的一个特例，但这已经能证明，即便是在消解后严格领先的分支进行分别格式化，依然有可能扩大冲突块的规模。</p><h2 id="使用两步格式化策略后的-hotfix-合并"><a href="#使用两步格式化策略后的-hotfix-合并" class="headerlink" title="使用两步格式化策略后的 hotfix 合并"></a>使用两步格式化策略后的 hotfix 合并</h2><p>使用两步格式化后的 git 历史如图。</p><pre>  ----C---E master /     \   \A---B---D---F---G develop</pre><p>其中 E 是对 master 分支的格式化，G 是对 develop 分支的增量格式化，我们下面对 master 分支进行一个 hotfix G</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">diff --git a/index.js b/index.js</span></span><br><span class="line"><span class="comment">index 93d99d6..fad8cfb 100644</span></span><br><span class="line"><span class="comment">--- a/index.js</span></span><br><span class="line"><span class="comment">+++ b/index.js</span></span><br><span class="line"><span class="meta">@@ -1,3 +1,6 @@</span></span><br><span class="line"> module.exports = function(foo) &#123;</span><br><span class="line"><span class="addition">+  if (foo === undefined) &#123;</span></span><br><span class="line"><span class="addition">+    return undefined;</span></span><br><span class="line"><span class="addition">+  &#125;</span></span><br><span class="line">   return &quot;foo&quot; + &quot;bar&quot;;</span><br><span class="line"> &#125;;</span><br></pre></td></tr></table></figure><p>此时 F 的文件内容为</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + bar();</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>显然，G 的改动将会与 F 冲突，此时将 G 合并到 F，冲突如下</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">foo</span>) </span>&#123;</span><br><span class="line">&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + bar();</span><br><span class="line">=======</span><br><span class="line">  <span class="keyword">if</span> (foo === <span class="literal">undefined</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;foo&quot;</span> + <span class="string">&quot;bar&quot;</span>;</span><br><span class="line">&gt;&gt;&gt;&gt;&gt;&gt;&gt; master</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>冲突块清晰地定位在<code>module.exports</code>函数的变更冲突，master 对<code>foo</code>做了校验，develop 又重构了<code>bar</code>函数，与上面例子中<code>module.exports = function(foo) &#123;</code>都被标记成冲突比起来，此时代码风格的变更均不引起冲突。业务逻辑导致的代码冲突被隔离了出来。</p><p>最后，你可以到<a href="https://github.com/JLHwung/format-whole-codebase-playground">这个项目</a>查看上面描述到的分支历史。</p><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><blockquote><p>如何在 git blame 中忽略掉庞大的全量格式化 commit，保证 git blame 的返回某一行与业务相关的修改信息？</p></blockquote><p>截至2017年12月，git blame 不支持忽略某一个 commit。 Google 的 chrome 团队架构工具提供了 git blame 的一个替代命令：<a href="https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/git-hyper-blame.html">git-hyper-blame</a>, 该命令支持项目指定 git blame 忽略掉一些特定 commit。macOS 用户可以通过</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">brew tap easyops-cn/homebrew-chromium-tools</span><br><span class="line">brew install depot_tools</span><br></pre></td></tr></table></figure><p>来安装。只有<code>.git-blame-ignore-revs</code>文件中有全量格式化的 commit，例如</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">21dad2b6c508a637be41de85cac07699ddbd1485 #格式化代码</span><br></pre></td></tr></table></figure><p>那么使用<code>git hyper-blame</code>就可以指定 blame 的时候忽略 21dad2b6c508a637be41de85cac07699ddbd1485 这个 commit。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>只要格式化工具的测试样例覆盖了足够全的语法结构，全量格式化并不是一件风险不可控的事情。对于 GitFlow 分支模型的项目，从 master 分支出发，按照合并——消解——增量格式化的两步格式化策略，就可以轻松地让整个项目代码风格统一。后续配合 pre-commit 的自动格式化钩子，可以保证项目的代码风格是永续一致的。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;全量格式化还是渐进格式化？&quot;&gt;&lt;a href=&quot;#全量格式化还是渐进格式化？&quot; class=&quot;headerlink&quot; title=&quot;全量格式化还是渐进格式化？&quot;&gt;&lt;/a&gt;全量格式化还是渐进格式化？&lt;/h2&gt;&lt;p&gt;对于一个历史悠久又没有执行强制代码规范的代码库，全量</summary>
      
    
    
    
    
    <category term="git" scheme="https://jhuang.me/tags/git/"/>
    
    <category term="formatter" scheme="https://jhuang.me/tags/formatter/"/>
    
  </entry>
  
  <entry>
    <title>使用 CSS Houdini 绘制平滑圆角</title>
    <link href="https://jhuang.me/2017/11/07/%E4%BD%BF%E7%94%A8-CSS-Houdini-%E7%BB%98%E5%88%B6%E5%B9%B3%E6%BB%91%E5%9C%86%E8%A7%92/"/>
    <id>https://jhuang.me/2017/11/07/%E4%BD%BF%E7%94%A8-CSS-Houdini-%E7%BB%98%E5%88%B6%E5%B9%B3%E6%BB%91%E5%9C%86%E8%A7%92/</id>
    <published>2017-11-07T18:01:42.000Z</published>
    <updated>2018-04-01T02:09:19.903Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="http://iamvdo.me/en/blog/smooth-corners-with-css-houdini">http://iamvdo.me/en/blog/smooth-corners-with-css-houdini</a><br>已得到原文作者 <a href="http://iamvdo.me/en">Vincent De Oliveira</a> 授权翻译</p></blockquote><p>最近，我在推特<a href="https://twitter.com/iamvdo/status/908308376886575104">分享</a>了一篇关于<a href="https://medium.muz.li/optical-effects-9fca82b4cd9a">人机交互界面的视错觉</a>的文章。我向来喜欢视错觉，但这篇文章传达了一个新观点：与几何上的正圆比起来，一个微调过的圆形有可能给人以更加圆的感觉，而这一点对于圆角矩形同样适用。同时，我也惊奇地发现苹果从 iOS 7 开始就对系统图标做了同样的调整。在数学上，我们称这个调整过的圆角矩形在为<a href="https://baike.baidu.com/item/%E8%B6%85%E6%A4%AD%E5%9C%86">超椭圆</a>。</p><a id="more"></a><figure>![iOS 6 与 iOS 7 的图标外形差异](/img/ios6-ios7.jpg)<figcation>iOS 6 与 iOS 7 的图标外形差异（[来源](https://ivomynttinen.com/blog/ios-design-guidelines)）</figure><p>与此同时，为了准备一场讲演，我做了一些关于 CSS Houdini 的绘图 API（Paint API）的实验。这个 API 定义了一种新的在浏览器的渲染阶段往 CSS 图像中添加内容的方式。简单地说，浏览器给我们提供了用程序绘制一个 CSS 图像并用作背景的能力。因此，我们应该可以很简单地绘制一个超椭圆。</p><p>几个星期后，Sketch 添加了一个平滑圆角（smooth corners）的功能。据我所知，平滑圆角实际上就是超椭圆。我喜欢「平滑圆角」这个名字，于是在这里给大家展示如何使用 CSS 绘制平滑圆角。</p><p>首先，我们给<code>paintWorklet</code>添加一个绘图模块<sup><a href="#ref1" name="fnref1">1</a></sup>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(CSS.paintWorklet || paintWorklet).addModule(<span class="string">&#x27;smooth-corners.js&#x27;</span>)</span><br></pre></td></tr></table></figure><p>然后，在这个模块中，我们注册一个绘图过程（paint），名字叫做 <code>smooth-corners</code>，这个绘图过程必须提供一个<code>paint</code>方法来绘制超椭圆。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">registerPaint(<span class="string">&#x27;smooth-corners&#x27;</span>, <span class="class"><span class="keyword">class</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="title">paint</span>(<span class="params">ctx, size</span>)</span> &#123;</span><br><span class="line">    ctx.fillStyle = <span class="string">&#x27;black&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// n=4 时，绘制一个方圆形</span></span><br><span class="line">    <span class="keyword">const</span> n = <span class="number">4</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> m = n</span><br><span class="line">    <span class="keyword">if</span> (n &gt; <span class="number">100</span>) m = <span class="number">100</span></span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">0.00000000001</span>) m = <span class="number">0.00000000001</span></span><br><span class="line">    <span class="keyword">const</span> r = size.width / <span class="number">2</span></span><br><span class="line">    <span class="keyword">const</span> w = size.width / <span class="number">2</span></span><br><span class="line">    <span class="keyword">const</span> h = size.height / <span class="number">2</span></span><br><span class="line"></span><br><span class="line">    ctx.beginPath();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; (<span class="number">2</span>*r+<span class="number">1</span>); i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> x = (i-r) + w</span><br><span class="line">      <span class="keyword">const</span> y = (<span class="built_in">Math</span>.pow(<span class="built_in">Math</span>.abs(<span class="built_in">Math</span>.pow(r,m)-<span class="built_in">Math</span>.pow(<span class="built_in">Math</span>.abs(i-r),m)),<span class="number">1</span>/m)) + h</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (i == <span class="number">0</span>)</span><br><span class="line">        ctx.moveTo(x, y)</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">        ctx.lineTo(x, y)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = (<span class="number">2</span>*r); i &lt; (<span class="number">4</span>*r+<span class="number">1</span>); i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> x = (<span class="number">3</span>*r-i) + w</span><br><span class="line">      <span class="keyword">const</span> y = (-<span class="built_in">Math</span>.pow(<span class="built_in">Math</span>.abs(<span class="built_in">Math</span>.pow(r,m)-<span class="built_in">Math</span>.pow(<span class="built_in">Math</span>.abs(<span class="number">3</span>*r-i),m)),<span class="number">1</span>/m)) + h</span><br><span class="line">      ctx.lineTo(x, y)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ctx.closePath()</span><br><span class="line">    ctx.fill()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><code>paint</code>方法接受两个参数：</p><ul><li><code>ctx</code>是一个<code>PaintRenderingContext2D</code>对象，这个对象实现了<code>CanvasRenderingContext2D</code>的一个子集，因此大多数情况下你可以用它绘制任何图形</li><li><code>size</code>是一个<code>PaintSize</code>对象，规定所绘制图形的大小</li></ul><p>现在我们可以在 CSS 中调用这个<code>paint()</code>函数。执行这个函数，我们将会得到一个黑色的平滑圆角矩形。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.el</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">paint</span>(smooth-corners);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了简单起见，我们将生成的图像用作图层遮罩（mask）<sup><a href="#ref2" name="fnref2">2</a></sup>，这样，我们就可以很容易地通过<code>background</code>设置想要的背景色、渐变、或者图像。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.el</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(deeppink, orangered);</span><br><span class="line">  <span class="attribute">mask-image</span>: <span class="built_in">paint</span>(smooth-corners);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure>![CSS实现的平滑圆角](/img/squircle.jpg)<figcaption>CSS实现的平滑圆角（[来源](http://iamvdo.me/content/01-blog/31-smooth-corners-avec-css-houdini/squircle.jpg)）</figcaption></figure><p>视觉效果不错，但程序灵活性还不够。现在，我们只是画了一种特殊的超椭圆——方圆形<sup><a href="#ref3" name="fnref3">3</a></sup>（注意代码中<code>n = 4</code>）。那么，我们如何画任意指数的超椭圆呢？比如说 iOS 使用了 <code>n = 5</code>。我们可以使用 CSS 自定义属性来达到目的。</p><p>首先，定义自定义属性<code>--smooth-corners</code></p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.el</span> &#123;</span><br><span class="line">  <span class="attribute">--smooth-corners</span>: <span class="number">4</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(deeppink, orangered);</span><br><span class="line">  <span class="attribute">mask-image</span>: <span class="built_in">paint</span>(smooth-corners);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后从<code>registerPaint</code>方法中获取</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">registerPaint(<span class="string">&#x27;smooth-corners&#x27;</span>, <span class="class"><span class="keyword">class</span> </span>&#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">get</span> <span class="title">inputProperties</span>() &#123;</span><br><span class="line">      <span class="keyword">return</span> [</span><br><span class="line">          <span class="string">&#x27;--smooth-corners&#x27;</span></span><br><span class="line">      ]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="title">paint</span>(<span class="params">ctx, size, styleMap</span>)</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> exp = styleMap.get(<span class="string">&#x27;--smooth-corners&#x27;</span>).toString()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> n = exp</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>注意到<code>paint()</code>方法接受第三个参数<code>styleMap</code>，这是一个<code>StylePropertyMapReadOnly</code>对象，本质上是一个 Map 对象，通过此对象可以获取在<code>inputProperties</code>定义过的属性的计算值。这里我们获取了<code>--smooth-corners</code>的属性值并传递给<code>n</code>.</p><p>至此，我们可以在 CSS 中使用<code>--smooth-corners</code>属性，这个属性甚至还可以被 CSS 动画动态调整，只要我们通过<code>CSS.registerProperty</code>注册这一属性（<a href="http://lab.iamvdo.me/houdini/animating-gradient">示例</a>，参考 <a href="https://drafts.css-houdini.org/css-properties-values-api/">CSS 属性与值 API</a>）。</p><p>截至发稿时，只有 Chrome 支持 Houdini 的 Paint API，因此我们使用渐进增强的方式提供绘制平滑圆角</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.el</span> &#123;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">60px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(deeppink, orangered)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@supports</span> (<span class="attribute">mask-image:</span> paint(smooth-corners)) &#123;</span><br><span class="line">  <span class="selector-class">.el</span><span class="selector-class">.is-loaded</span> &#123;</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">mask-image</span>: <span class="built_in">paint</span>(smooth-corners);</span><br><span class="line">    <span class="attribute">--smooth-corners</span>: <span class="number">5</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除此之外，因为 Houdini 是一个 JS-in-CSS 方案，我们最好等到 JavaScript 加载了之后再施加 CSS 属性。因此，我决定在元素上加上<code>.is-loaded</code>类来确保加载后应用样式。</p><p>在生产环境中，我们应该使用 PostCSS 插件来自动添加<code>--smooth-corners</code>的 CSS 实现。（译者注：截至发稿时还没有对应的 PostCSS 插件）</p><p>你可以使用支持这一特性的浏览器到 <a href="http://lab.iamvdo.me/houdini/smooth-corners">http://lab.iamvdo.me/houdini/smooth-corners</a> 体验最终效果。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>简单来讲，使用 CSS 遮罩就是把路径外的元素遮盖掉（这也是遮罩的目的😁）。如果有需要，你也可以使用<code>registerPaint</code>直接绘制渐变或者图像（图像似乎目前支持很有限，你只能自己处理图像的情况）。</p><p>如果你想自己动手，可以参考其他绘制图像的实例：<a href="http://lab.iamvdo.me/houdini/background-properties">创建你自己的背景属性，比如<code>background-opacity</code></a>或者以属性之外的方式传递绘图参数：<a href="http://lab.iamvdo.me/houdini/corners-gradient">绘制四个角落出发的渐变</a>。我很乐意分享你们的示例。</p><hr><small><a name="ref1">1</a>: Chrome 需要打开`#enable-experimental-web-platform-features`标记. `PaintWorklet` 要么在`CSS`下（对应稳定版），要么在`window`下（对应金丝雀版本）。（译者注：在 Chrome 61 及其以下版本，`PaintWorklet`在`window`下，Chrome 62 及其以上版本，`PaintWorklet`在`CSS`下[↩](#fnref1)<a name="ref2">2</a>: 不要忘了使用`Autoprefixer`来自动生成浏览器前缀[↩](#fnref2)<a name="ref3">3</a>: 方圆形（squircle）一词是方形（square）与圆形（circle）的融合[↩](#fnref3)</small>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;http://iamvdo.me/en/blog/smooth-corners-with-css-houdini&quot;&gt;http://iamvdo.me/en/blog/smooth-corners-with-css-houdini&lt;/a&gt;&lt;br&gt;已得到原文作者 &lt;a href=&quot;http://iamvdo.me/en&quot;&gt;Vincent De Oliveira&lt;/a&gt; 授权翻译&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近，我在推特&lt;a href=&quot;https://twitter.com/iamvdo/status/908308376886575104&quot;&gt;分享&lt;/a&gt;了一篇关于&lt;a href=&quot;https://medium.muz.li/optical-effects-9fca82b4cd9a&quot;&gt;人机交互界面的视错觉&lt;/a&gt;的文章。我向来喜欢视错觉，但这篇文章传达了一个新观点：与几何上的正圆比起来，一个微调过的圆形有可能给人以更加圆的感觉，而这一点对于圆角矩形同样适用。同时，我也惊奇地发现苹果从 iOS 7 开始就对系统图标做了同样的调整。在数学上，我们称这个调整过的圆角矩形在为&lt;a href=&quot;https://baike.baidu.com/item/%E8%B6%85%E6%A4%AD%E5%9C%86&quot;&gt;超椭圆&lt;/a&gt;。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
</feed>
