無文字 | 三無計劃https://blog.imalan.cn/只坚持一种正义。我的正义。http://www.rssboard.org/rss-specificationpython-feedgenhttps://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/android-chrome-512x512.png無文字 | 三無計劃https://blog.imalan.cn/zh-CNTue, 14 Jan 2020 12:54:18 +0806Tue, 14 Jan 2020 12:54:18 +0806泸沽湖游记https://blog.imalan.cn/archives/422/<p>今年暑期觅得两周空闲,赶紧收拾行李赶去与家人朋友相聚。人也真是奇怪,工作时盼着放假,真要放假了却又闲不住。想起之前就与朋友有约要出去自驾游,刚巧几个好友都能凑出时间,那就出发吧!</p> <p>这次的目的地是泸沽湖,位于四川与云南交界。该景点由于「亲爱的客栈」播出而名声大振,游人络绎不绝,物价也水涨船高。然而终究受制于当地落后的管理与建设,设施与服务都不尽人意。</p> <hr> <p>7 月 31 日,一行五人晚饭后从成都出发,驾车至雅安市驶上成昆高速雅西段,直奔西昌。从雅安开始就多为山路,虽已是盛夏,随着海拔攀升再加上一路的绿水青山,倒也不觉得烦闷。大家都喜欢音乐,于是在车内一人一首地点歌,会唱的跟唱,气氛非常欢乐。</p> <p><figure style="flex: 71.39784946236558" ><img width="1328" height="930" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/9cc7da5d8406117a733dc659d21a50c9.png" /></figure></p> <p>一路上总体顺利,但仍然有个惊险的插曲。雅西高速西昌出口附近正在施工,道路狭窄,我们跟在一辆小车后面行驶时前方突然急刹并左右摇摆,我们也赶紧急刹,正纳闷怎么回事时发现路面上有很多大小不一的黑色物体,有的高度超过底盘,于是立刻降速并且左右躲闪。最终仍然有一块物体擦到了底盘,幸亏后来检查并无大碍。这段路照明不佳,想起来还是十分后怕,若是驾驶员精神稍有涣散没有及时反应,最终的结果肯定是事故。</p> <p>至今也没想明白黑色物体从何而来,或许是山间横风从工地中吹来的,或许是前车掉落的货物。到达西昌已是晚间十点过。五人点外卖填填肚子就洗漱睡觉,准备迎接第二日的旅程。</p> <hr> <p>第二日从西昌向泸沽湖景区出发。除开始的一小段高速外就是弯弯曲曲的省道,全是山路,仅 300 多千米的路程足足开了七八个小时。</p> <p><figure style="flex: 96.32034632034632" ><img width="1780" height="924" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/9e1c0a715b8637362026fa24ddd90565.png" /></figure></p> <p>一路上肉眼可见风土人情变迁。西昌已是川内出名的民风剽悍之地,但毕竟是市;越接近泸沽湖则越有待开发的少数民族聚集地之感,常住人口也主要是彝族、藏族。我幼年住在藏族自治州,对这种环境甚是熟悉,见此番景观觉得十分亲切。其实不太好形容这类区域给我的感觉,想了半天仍然不能离开「蛮荒」这个听上去有些贬义的词,但必须明白,蕴含在蛮荒背后的是活力、生机与无限的可能性。</p> <p>路途遥远,我们不时停车在路边吃零食、聊天、小憩。比起成都平原潮湿闷热的空气,这里显得干爽清新不少。</p> <hr> <p>大越下午三点到达预定的民宿。住处共两层,楼下是一张大床,我们三个男生准备挤在上面;楼上其实是阁楼,有一张小床,给两位女生住。巨大的落地窗占满了一面墙,屋内明亮宽敞。可惜没留下照片。</p> <p>我们安顿好,稍微吃了点干粮,迫不及待地想要去看看湖。民宿距湖边还有一段距离,仍要驾车前往,此时已经快到晚饭时间,于是一瞥美景,收获了下面的照片。</p> <p><figure style="flex: 135.45454545454547" ><img width="3427" height="1265" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/478af0662a61bd10c2e850b38546222d.jpg" /></figure></p> <p>至此我对景区有了大概的印象。据说泸沽湖目前正在创建五星级景区,但目前来看还差得太远。虽然有一等的景色,但管理与开发实在太差。基础设施缺乏、湖边被当地居民的摊点占据,甚至拍照都有原住民在守着漫天要价。</p> <p>不过我想这其中的大问题还是出在原住民身上。他们受教育程度不高,能想到的也只是守着湖边卖点烤串饰品,无心也无力加入更赚钱的诸如开客栈之类的行列,在外来资本的挤压下,他们的生存空间只会更小。有时,他们对祖辈流传下来的生活方式有一种近乎固执的坚持,这时常成为一种阻碍。</p> <p>晚餐去了朋友推荐的一家餐馆,名叫「鱼味」,以景区的标准来看算是美味实惠。五人在半露天的二楼吃饭聊天,十分惬意。专门点了一道名叫「水性杨花」的小菜,这是一种水生植物,春夏季节会伸出水面开白色的花,远看似一片花海。离开的前一天晚我们又去了这家餐馆。同样的座位,服务员换成了一位当地的小姑娘,不到 20 岁的年龄,笑起来十分甜美。我们点饮料时习惯性地说要「肥宅快乐水」,她有点疑惑,我们笑着改口说就是可乐,姑娘也跟着捂嘴笑起来。结账时向店长夸赞这位姑娘做事干练,店长笑着回答说已经有不少客人这么说了,店里正在与她商量以后的职业道路。祝她好运。</p> <p>晚餐后去湖边散步,由于欠开发没有太多的光污染,得以一览湖面夜景。</p> <div class="photos"> <figure style="flex: 75.0" ><img width="4032" height="2688" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/33b9f85372ff7c6d9e92ed046a5df1a6.jpg" /></figure> <figure style="flex: 75.0" ><img width="4032" height="2688" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/6e602fcde69527879faafe827bd25c15.jpg" /></figure></div><p>买了一箱啤酒回到住处,五人玩德州扑克,筹码是喝酒,一口起跳,五口封顶。最终男生平均喝了四五瓶,女生可以折半,一人也是两瓶左右。到后半夜牌玩腻了,开始一句接一句地喝酒聊天,聊生活,聊以往的趣事(我们是初中同学),这一聊知晓不少八卦,甚是有趣。不知不觉已经凌晨三点过。</p> <p><figure style="flex: 66.66666666666667" ><img width="4032" height="3024" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/40db745231303a5830b686f926f11f28.jpg" /><figcaption>五人在床上玩德州扑克</figcaption></figure></p> <p>第二天的主角是泸沽湖。景区有一条环湖公路,驾车绕一圈即可把湖面大部分风光尽收眼底。适逢天气大好,景色美不胜收。</p> <p>第一项活动是划船。早晨的泸沽湖静谧宜人,微风不时吹起涟漪,不久便平静下来,又能尽览湖底植被。远处山腰云雾缭绕,如同仙境。</p> <p><figure style="flex: 135.45454545454547" ><img width="3427" height="1265" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/f7f62f32988992c3c651de3f6561ebc0.jpg" /></figure></p> <p>终于在湖面看到了「水性杨花」,但可惜很稀疏,据划船的大叔说原本很多的,但游人多起来后餐馆会偷偷摘走做菜,所以现在所剩无几。原来花海都被我们这些游客吃进肚了。划完船阳光逐渐热辣起来,幸亏起了个大早,躲过日晒。</p> <p>环湖公路沿途有许多观景台与沙石滩供游人逗留,我们挑选了几处下车。女生忙着拍照,男生顽劣,捡起石子开始打水漂。</p> <div class="photos"> <figure style="flex: 37.19135802469136" ><img width="723" height="972" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/2e94468b39c2e16ed4ef39b1fa68fd86.jpg" /></figure> <figure style="flex: 66.66666666666667" ><img width="4032" height="3024" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/52b16aa7674ab433e90cb8b725444922.jpg" /></figure></div><div class="photos"> <figure style="flex: 66.66666666666667" ><img width="4032" height="3024" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/f830c8235706396d66607283dbccc5f6.jpg" /></figure> <figure style="flex: 50.0" ><img width="3024" height="3024" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/7041412a8f3afb34d62c5f1167f25b9e.jpg" /></figure></div><div class="photos"> <figure style="flex: 135.45454545454547" ><img width="3427" height="1265" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/62856fe4c051ea1fc3d39322a1f162d0.jpg" /></figure></div><p>边走边玩,终于到了里格观景台,在这里留下了我最爱的一张照片:</p> <p><figure style="flex: 135.45454545454547" ><img width="3427" height="1265" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/db5320a0a1f5b0299265621cf9eb5fa3.jpg" /></figure></p> <p>我几乎不能形容看到这景象时的感受。脸上是拂来的微风,视野被硕大的湖面占满,游人的喧闹听不见,毒辣的阳光感受不到,所谓沉浸不过如此。其实已经看过很多壮美的景观了,但这次还是被震撼:蕴含其中的,是大自然怎样的精巧与善意呢?</p> <hr> <p>还有个打卡处名叫「走婚桥」,名字来源于当地摩梭族的走婚传统。桥上有人给人编发辫,我们怂恿两位女生试了试,还真挺好看。</p> <div class="photos"> <figure style="flex: 62.24361657597321" ><img width="2974" height="2389" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/56e78d92249f9754ca6d1c77904c25c3.jpg" /></figure> <figure style="flex: 42.87979244739118" ><img width="2975" height="3469" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/953da01de5b7d3e3585c332faa9c08d7.jpg" /></figure></div><p>走婚桥上人特别多,也许大家都想蹭蹭「过桥者能长长久久」的彩头。不过桥头遇到一对吵架的情侣,吵架方式挺奇特,就像在谈合同。</p> <p>晚上回到住处,打开阁楼天窗发现夜空晴朗,便有人拿出手机张罗着要拍星星。拍星星的忙着,其他人就每人一瓶啤酒听歌聊天,分享各自拍的照片。</p> <p>大家都比较疲惫,这一晚休息得早。</p> <hr> <p>第二天返程全天都在车上度过,听歌、聊天、吃零食、睡觉。到家已经是晚上九点过,至此泸沽湖之旅圆满结束,大家纷纷在群里晒照片,把几个没去成的朋友气得牙痒痒。</p> <p>有人说我们几个人一起出去玩时是最轻松的,不用担心能不能玩到一块儿,没有拧巴的人,不用端着,互相知根知底,什么都能聊,没有人扫兴。</p> <p>这大概就是十多年的友情吧。说到底,是景色重要,还是游玩重要,还是与友人共度的时光重要呢?对我来说答案倒是显而易见的。</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/422/Sun, 25 Aug 2019 17:54:00 +0806聊聊「杠精」与评论管理https://blog.imalan.cn/archives/441/<p>但凡管理社区、网站的博主应该多少都有过这方面的困扰。正巧昨天在一篇文章下面出现了一串<a href="https://blog.imalan.cn/archives/422/comment-page-1#comment-3119">讨论</a>,气氛到后来变得不太舒服。我想聊聊我的看法。</p> <p>网民形形色色,人与人之间所持立场、思考方式本就大相径庭,出现意见向左或者针锋相对的情况实属再正常不过。因此我并不赞成现在中文互联网上用「杠精」的棒子把异见一概打死的风气。</p> <p>为了讨论这个问题,我们首先需要弄明白「杠」到底是什么。</p> <p>有水平的「杠」绝不是谬误,他们本身常是逻辑自洽的,只是偏偏选择了一个博主无意强调的或者不想讨论的切入点,置原文主题不顾;抑或是专门把事情往坏处想、往坏处说,因此很难驳倒他们。前面一种情况的例子如<a href="https://blog.imalan.cn/archives/422/">《泸沽湖游记》</a>一文下的评论:</p> <blockquote><blockquote><p>买了一箱啤酒回到住处,五人玩德州扑克,筹码是喝酒,一口起跳,五口封顶。最终男生平均喝了四五瓶,女生可以折半,一人也是两瓶左右。</p> </blockquote> <p>此处有问题。三男两女,4×3+2×2=16。一箱不够分的。一定是你喝多了,有人耍赖没发现。除非是24瓶的大箱。</p> <p>为什么过 “走婚桥” 的寓意是 “过桥者能长长久久”?莫不是我对走婚二字理解有误?</p> </blockquote> <p>后者如<a href="https://blog.imalan.cn/archives/317/">《春天、联谊、新耳机》</a>下的评论:</p> <blockquote><p>据我所知,有的联谊会花钱雇女生来撑场面,不知道你这个遇到的是李逵还是李鬼。</p> </blockquote> <p>这些评论说错了吗?并没有。酒的数量确实说不过去,我肯定是记错了;「走婚桥寓意长长久久」在文章结尾稍带提到了,但显然也不是本文重点;可能确有联谊请女生撑场面,但是评论者并不知道我是去北师大与教育学部的女生们联谊,组织者是两校研究生会,但凡了解教育学部的人都应该知道实在没必要请女生来撑场面,但也不好说他一定错了。</p> <p>既然没有说错,也没有出现恶意中伤之类的行为,这些评论的存在就是正当的。</p> <p>但它们带来的不适感也同样实实在在。如何让访客畅所欲言的同时维护良性的讨论氛围、避免出现「杠」的同时不损害访客的发言权,实在是个难题。</p> <p>首先可以确定的是:「删帖」属于下策。删掉发言既不利于把问题讲清楚,也不能从本质上改善社区环境,只会让访客觉得管理员是一个独裁者。即使是令人不适的发言,保留完整的语境供大众评说是更妥当的处理方案。发声是每个人的权利,大家都希望自己的声音能被人听到,这是自然的。「删帖」不折不扣地损害了该权利,此举长期来看不利于社区建设。不能自由说话的社区没有吸引力也没有生命力。</p> <p>且不说我等微小的个人博客,大型的社区也苦评论管理已久。Twitter 长期受平台上的仇恨言论困扰,就连 GitHub 也不能杜绝以中国人为首的 issue 区灌水行为。</p> <p>删又删不得,骂又骂不得,只能退而求其次把这些评论隐藏起来,求个眼不见心不烦。这种途径国内早有平台采用,例如在知乎上达到某些条件的评论会被<a href="https://www.zhihu.com/question/20120168">折叠</a>;少数派的<a href="https://sspai.com/post/55040">评论规范</a>规定当踩高于赞时自动折叠评论,<a href="https://sspai.com/post/53759">这篇文章</a>的评论区里连老麦(少数派创始人)自己的评论都被折叠了。</p> <p>无奈之下,本站也准备尝试这种方案。昨晚我在主题开发版中增加了评论投票的功能,访客可对评论点赞或点踩,当踩的数量超过阈值且踩赞比也高于阈值时,评论会被折叠。当然即使被折叠了也可以手动展开查看,但多少可以告诫别的访客以及评论发起者:这条评论收到了许多 dislike,可能包含过激、断章取义等令人不适的内容。虽然这是个人博客,但也不是一言堂,把决定权交给看客也算是公允吧。</p> <p>也有博主觉得烦不胜烦索性关掉评论,直接表示「JOJO 我不做社区了!」,也值得尊重吧。Do more, hassle less.</p> <hr> <p>特别说明:本文讨论不适用于 spam,我认为广告、灌水评论直接删掉无妨。</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/441/Wed, 28 Aug 2019 22:47:00 +0806使用 Grid-Stride Loop 复用 CUDA 线程https://blog.imalan.cn/archives/446/<p>在阅读 CUDA 文档的过程中看到这一篇博文:<a href="https://devblogs.nvidia.com/cuda-pro-tip-write-flexible-kernels-grid-stride-loops/">CUDA Pro Tip: Write Flexible Kernels with Grid-Stride Loops</a>,觉得是很不错的思想(技巧),因此记录下来,并且附上一些验证数据。</p> <h3>In theory</h3> <p>问题起源:一般使用 CUDA 并行计算时,总使用一个线程对应一块数据,例如计算两向量之和:</p> <div class="highlight"><pre><span></span><span class="c1">// device code to compute c = a + b;</span> <span class="c1">// this method assumes we have enough threads to do the computation</span> <span class="c1">// enough means larger than the size of array a, b and c</span> <span class="n">__global__</span> <span class="kt">void</span> <span class="nf">addArray</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">b</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// assume we have 1k elements to compute, in this way</span> <span class="c1">// thread 0 is responsible for c[0] = a[0] + b[0]</span> <span class="c1">// thread 1 is responsible for c[1] = a[1] + b[1]</span> <span class="c1">// and so on</span> <span class="k">auto</span> <span class="n">index</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">)</span> <span class="n">c</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">+</span> <span class="n">b</span><span class="p">[</span><span class="n">index</span><span class="p">];</span> <span class="p">}</span> <span class="cp">#define ARRAYSIZE 1000000</span> <span class="cp">#define BLOCKSIZE 1024 </span><span class="c1">// can be any size, better be multiple of 32</span> <span class="n">__host__</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// ...</span> <span class="c1">// calculate the minmal number of blocks to cover all data</span> <span class="k">auto</span> <span class="n">numBlocks</span> <span class="o">=</span> <span class="p">(</span><span class="n">ARRAYSIZE</span> <span class="o">+</span> <span class="n">BLOCKSIZE</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="n">BLOCKSIZE</span><span class="p">;</span> <span class="n">addArray</span> <span class="o">&lt;&lt;&lt;</span><span class="n">numBlocks</span><span class="p">,</span> <span class="n">BLOCKSIZE</span> <span class="o">&gt;&gt;&gt;</span> <span class="p">(</span><span class="n">ARRAYSIZE</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">C</span><span class="p">);</span> <span class="c1">// ...</span> <span class="p">}</span> </pre></div> <p>如我们所知,CUDA 中线程组织结构从高到低分为 Grid、Block、Warp 三层,这三层中,每一层都有自己能容纳的最大线程数量,例如在我的设备(1080Ti)上,每个 Block 最多容纳 1024 个线程,每个 Warp 固定是 32 个线程,而每个 Grid 能容纳的数量就比较大了(2147483647, 65535, 65535)。同时还有一个条件,每个 multiprocessor 最大容纳 2048 个线程。总而言之,GPU 虽然能提供大量的线程,但并不是无限的。</p> <p>在不讨论显存大小的前提下,总可能出现这样的情况:程序需要的线程数量大于 GPU 可以提供的线程数量。此时上面的代码就不行了,但下面这种处理方式就可以适应:</p> <div class="highlight"><pre><span></span><span class="c1">// another version to compute c = a + b</span> <span class="c1">// this uses grid loop to reuse some threads(if needed)</span> <span class="n">__global__</span> <span class="kt">void</span> <span class="nf">addArray_gird_loop</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">b</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// total number of threads in one grid</span> <span class="c1">// which is accessible to one kernel</span> <span class="k">auto</span> <span class="n">stride</span> <span class="o">=</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">gridDim</span><span class="p">.</span><span class="n">x</span><span class="p">;</span> <span class="k">auto</span> <span class="n">index</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span> <span class="c1">// compute using the whole grid at once</span> <span class="c1">// the reuse the threads in the same grid</span> <span class="c1">// assume we have 1k elements to compute, and a stride of 100</span> <span class="c1">// thread 0 is responsible for c[0] = a[0] + b[0]</span> <span class="c1">// c[100] = a[100] + b[100]...</span> <span class="c1">// thread 1 is responsible for c[1] = a[1] + b[1]</span> <span class="c1">// c[101] = a[101] + b[101]...</span> <span class="c1">// and so on</span> <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">index</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">stride</span><span class="p">)</span> <span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="p">}</span> <span class="cp">#define ARRAYSIZE 1000000</span> <span class="cp">#define BLOCKSIZE 1024 </span><span class="c1">// can be any size, better be multiple of 32</span> <span class="cp">#define GRIDSIZE 10 </span><span class="c1">// can be any size, better be multipel of multiprocessor count</span> <span class="n">__host__</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// ...</span> <span class="n">addArray_gird_loop</span> <span class="o">&lt;&lt;&lt;</span><span class="n">GRIDSIZE</span><span class="p">,</span> <span class="n">BLOCKSIZE</span> <span class="o">&gt;&gt;&gt;</span> <span class="p">(</span><span class="n">ARRAYSIZE</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">C</span><span class="p">);</span> <span class="c1">// ...</span> <span class="p">}</span> </pre></div> <p>在这种实现中,每个线程并不只负责一块数据,而是负责从 index 开始,stride 为步长的一组数据。在 kernel 中每个 for 循环的步长(Stride)刚好是一个 Grid 的大小,因此称作(Grid-Stride Loop)。</p> <p>这种实现的特征是 Grid 的大小可以是任意的,因此解决了「线程不够用」的问题。应该注意到,当 Grid 的大小足够大时(大于等于上一种方法计算出的 <code>numBlocks</code>),这种方法就退化到第一种方法。</p> <p>另外还需要注意到,当 <code>GRIDSIZE</code> 与 <code>BLOCKSIZE</code> 都取 1 时,GPU 里实际上只有一个线程在跑,于是退化为串行程序。</p> <p>文首提到的文章总结了这种思路的几个好处:</p> <ul> <li><p><strong>伸缩性与线程复用</strong>。可扩展性是指,这种方法在理论上可以支持任意规模的并行计算,而不受设备提供的最大线程数限制;另外这种实现允许我们采用更合理的 <code>GRIDSIZE</code>,比如常推荐的,使用 multiprocessor 数量的倍数。线程复用则可以帮助程序省去线程启动与销毁的开销。</p> </li> <li><p><strong>易于调试</strong>。如上文所述,当 <code>GRIDSIZE</code> 与 <code>BLOCKSIZE</code> 都取 1 时程序实际退化为串行程序,这为调试提供了方便(例如在 kernel 中使用 printf 可以得到顺序的结果)。</p> </li> <li><p><strong>可移植性与可读性</strong>。这种的写法可以轻易地修改为 CPU 代码,另外还有类似 <a href="https://devblogs.nvidia.com/parallelforall/simple-portable-parallel-c-hemi-2/">Hemi</a> 这样的库专门为 Grid-Stride Loop 提供支持,带来了 C++ 11 风格的循环语法:</p> <div class="highlight"><pre><span></span><span class="n">HEMI_LAUNCHABLE</span> <span class="kt">void</span> <span class="nf">addArray</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">b</span><span class="p">,</span> <span class="kt">float</span><span class="o">*</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="nl">i</span> <span class="p">:</span> <span class="n">hemi</span><span class="o">::</span><span class="n">grid_stride_range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">n</span><span class="p">))</span> <span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">];</span> <span class="p">}</span> </pre></div> </li> </ul> <h3>In real world</h3> <p>理论上说 Grid-Stride Loop 一切都很美好,但是由于每个线程要进行循环,想必会影响性能,这里我给出自己的测试结果。</p> <p>测试设备:GeForce GTX 1080Ti</p> <pre><code>CUDA Driver Version / Runtime Version 9.0 / 9.0 CUDA Capability Major/Minor version number: 6.1 Maximum number of threads per multiprocessor: 2048 Maximum number of threads per block: 1024 Max dimension size of a thread block (x,y,z): (1024, 1024, 64) Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)</code></pre> <p>首先看不使用 Grid-Stride Loop(GS Loop) 的方法中 <code>GRIDSIZE</code> 和时间相对 ARRAYSIZE 的变化:</p> <p><figure style="flex: 90.57788944723617" ><img width="721" height="398" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/fbae2a73d8c495f55a3c450dc010f93c.png" /></figure></p> <p>固定 <code>GRIDSIZE=10</code>,使用 Grid-Stride Loop 的方法:</p> <p><figure style="flex: 96.64879356568365" ><img width="721" height="373" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/119fca045d06f574cbbea83497ff9c82.png" /></figure></p> <p>直观对比两种方法用时:</p> <p><figure style="flex: 90.72681704260651" ><img width="724" height="399" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/2fe85e370fb8290b3184027d333d388f.png" /></figure></p> <p>可见当 ARRAYSIZE 较大时,两种方法性能有可观的差距。当然,这个测试只是理论上的,实际应用中不可能只用 10 个 Block 来计算。</p> <p>另外,若将显存大小纳入讨论则情况会稍有不同。向量相加这个例子中需要为相加的两个向量以及存储结果的一个向量在 GPU 上分配显存,有时会遇到这样的情况:线程还没用完,显存先用完了。这种情况与算法相关,并不是所有的程序都需要这么巨大的显存量,但需要巨大线程数的场景比比皆是。</p> <p>另外,如何将其扩展到高维 Tensor 还有待思考。</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/446/Sun, 29 Sep 2019 17:54:00 +0806国庆揽件:Apple Watch 五号机https://blog.imalan.cn/archives/apple-watch-series-5/<p>说起来,心情烦闷的时候就喜欢胡吃海喝、大手大脚花钱。国庆更是给我底气,「过节嘛,花点钱也无可厚非」。于是便有了本文主角:Apple Watch Series 5。</p> <p>这款手表今年 9 月随 iPhone 11 一同发布。一直以来,9 月的发布会都只有一个重头戏,因为任何产品都很难抢得过 iPhone 的风头,这次的 Apple Watch 也不例外。</p> <p>我的上一只 Apple Watch 还是 2015 年发布的「初号机」;之后苹果依次发布了 Series 1-4,直到今年 9 月的 Series 5。初号机发布后,虽然苹果每次发布会都强调「这是世界上卖得最好的智能手表」,甚至是「卖得最好的手表」,但仍无法掩盖市场需求疲软,销售不及预期的现实。很长一段时间里,苹果都拒绝公布 Apple Watch 具体销量。</p> <p>这与可穿戴设备逐渐退潮有关,也与初号机的定位定价有关。苹果一开始的算盘是打入时尚市场,走奢侈品路线,以致最贵的一款初号机售价高达 12 万人民币。但时尚界显然不买账,没有哪位名媛愿意戴着一只电子手表出席晚宴。</p> <p>苹果开始改变路线,转而聚焦健康领域,这才算是上道了。在后续机型中,苹果大幅改进了 Apple Watch 的传感器性能,陆续加入陀螺仪、GPS、罗盘、蜂窝网络;Series 4 中引入了电极式心率传感器,甚至还支持腕上 ECG;提高了防水级别,以方便游泳运动员使用。软件层面,诸如久坐提醒、正念、摔倒报警、心率报警、环境噪音报警等功能都是苹果的宣传重点。今年发布会上苹果播放了一个短片,由一些因 Apple Watch 而生活得更健康,甚至是捡回一命的人讲述自己的故事: <a href="https://www.youtube.com/watch?v=mx1by12-oF4">Dear Apple: Face to Face</a>。</p> <iframe data-ratio="0.625" src="//player.bilibili.com/player.html?aid=70338095&cid=121849487&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><p>现在看来,初号机是一款问题多多的产品。从手机到手表,设备本身的变化、应用场景的变化都十分巨大,新的交互模式也需要重新探索。特别让人印象深刻的一点是:初号机真的很慢。初号机的慢不是因为硬件过时,而是从一开始就没有给够性能。我想任何使用过初号机的人都能清晰地回忆起抬手打开一个 App 是什么样的体验。</p> <p>简单来说,煎熬得如同地狱。</p> <p>更不用提续航。习惯了一颗电池能用上一年半载的石英表,亦或是充一次电能用半月的智能手环,谁能忍受每天一充的手表呢?手表一天一充,simply makes no sense。</p> <p>以上这些怨念,有些延续至今,有些在迭代更替中得到解决或者改善,今年发布的 watchOS 6 与「五号机」则是苹果提交的最新答卷。从第一代开始见证一款产品的进化历程是非常有意思的事情,在其中,能看见商业公司的坚持与妥协,也能看见市场的起起伏伏。</p> <hr> <p>苹果对 Apple Watch 的重视程度从初号机的包装就能看出来,不同于简洁的 iPhone 包装盒,Apple Watch 产品线过度包装很严重。初号机长条形的包装盒拿在手上沉甸甸的,外面是苹果标志性的白色纸盒,里面还有一个精致的纯白塑料盒。细腻的阻尼感,严丝合缝的接口,处处体现着严谨的高级感,甚至让人无从下手。</p> <p>这次我从官网下单,预计到货是 12 号,实际上两天就拿到了包裹。外形仍然是长条形,纯白的纸盒上有 Apple Watch 字样的压印。</p> <p><figure style="flex: 91.08159392789373" ><img width="1920" height="1054" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/a7f8c6868a38442a0f8e5299baa11170.png" /></figure></p> <p>这只是外包装,展开后才是真正的包装盒。两个盒子叠在一起,上面是手表本体与充电线、适配器,下面则是表带。</p> <div class="photos"> <figure style="flex: 98.76543209876543" ><img width="1920" height="972" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/40a624fa7981217e367e3b8c6bb66955.png" /></figure> <figure style="flex: 69.97084548104957" ><img width="1920" height="1372" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/af7efa0895d08c5a470f818880802040.png" /></figure></div><p>表体被一块手感细腻的毛毡套保护起来。以前似乎没有这个设计,平添一丝高级感。刚好用来收纳我的初号机。</p> <div class="photos"> <figure style="flex: 50.0" ><img width="1920" height="1920" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/16e1eeb420a8de3cadc8e1af4d2acdf6.jpg" /></figure> <figure style="flex: 50.0" ><img width="1920" height="1920" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/916ed75c93e65197cd22edbc46f8c7bd.jpg" /></figure></div><p>完整的包装内容:</p> <p><figure style="flex: 86.33093525179856" ><img width="1920" height="1112" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/6942f2594829c015572fc04ee39d862b.png" /></figure></p> <p>与 iPhone 配对设置的过程就略过了。</p> <p>前段时间我还买了一只米家石英表。与其说它是智能手表,不如说是能连接手机的普通手表。本来的打算是用来看看时间,并设一个震动闹钟,以免手机闹钟吵醒室友,但没想到它的震动弱到难以察觉。可笑的是其包装上还堂而皇之地写着:「若感觉震动感太弱,请系紧表带」。我不太明白怎么才算紧?要勒到血流不畅吗?</p> <p>但米家石英表的好处是续航不错,外观也好看。作为一只「石英表」大概还是及格的。来张合照吧:</p> <p><figure style="flex: 103.8961038961039" ><img width="1920" height="924" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/6b69ecd903dbb899468d60b4f04f4362.png" /><figcaption>米家石英表、初号机、五号机</figcaption></figure></p> <p>我在微博上说:</p> <blockquote><p>从 Apple Watch 第一代直接跳到 Apple Watch Series 5,我真切地体会到了科技的进步。</p> </blockquote> <p>一方面,watchOS 愈发强大;另一方面,整个手表的运行速度大幅提升,再也不用抬着手等待 App 慢吞吞地启动了。五号机还带来了常亮屏幕,使其可用性提高了一个等级。</p> <p>对我来说,最重要的因素,也基本上是吸引我购买它的唯一因素,是其内置的陀螺仪、GPS 与心率传感器,这使我可以脱离 iPhone 只戴手表去跑步,同时得到准确的路径、速度与心率监控信息。另外,Watch 可以与 AirPods 无缝衔接,对我这个跑步必须听点东西的人来说这一点非常重要。</p> <p>但同时,这也是 watchOS 做得不太好的地方。要想在手表上独立听歌或者听播客,目前为止选择还是非常有限,似乎很少有开发者愿意真正地去优化手表 App 的体验。我目前的选择是 Overcast 听播客;iTunes 同步音乐至 iPhone 再同步至 Watch。Overcast 传输有时会不稳定,至于 iTunes 嘛……you know.</p> <p>今年苹果与几家机构合作,<a href="https://www.apple.com/newsroom/2019/09/apple-announces-three-groundbreaking-health-studies/">宣布</a>开启三项全新的研究计划,侧重女性健康、心脏与运动健康、听力健康,Apple Watch 在其中扮演关键角色。这似乎是理所应当的,除 Apple Watch 外,还有哪款产品能有如此广泛的受众,又有能取得如此丰富的传感器数据呢?</p> <p>绕不开的一个话题是隐私与数据安全。硅谷公司中,苹果一直扛着隐私保护的大旗,在这方面的立场最为鲜明,也最为苛刻,以至于许多分析师认为在隐私问题上拒绝让步已经对其产生了不利影响。一个例子是苹果拒绝将用户数据上传至服务器,而将依赖大量数据的机器学习算法全部放到用户本机上进行,许多人认为,这是阻碍苹果人工智能研究的一块绊脚石。</p> <p>虽然如此,作为消费者的我们最好还是保持警惕。商业公司总能找到出路,但泄漏的隐私不存在找回一说。在换脸 App、DeepNude 大行其道的今天,有必要搞明白一点:密码可以重设,但生物信息是不能改的。把生物认证信息泄露给不可靠的第三方,这就如同把银行卡密码广而告之;二者之间的主要差别是:前者影响更深远,而且目前看来没什么补救措施。</p> <p>话又说回产品本身,乔布斯也许不会同意这个观点,但我确实认为一款伟大的产品是商业公司与消费者共同造就的。Apple Watch 从一开始的奢侈品逐渐转变成侧重医疗健康的「实用品」,获得商业成功的同时朝着「make the world a better place」迈进了一步。这样的一番事业,也难怪有人会为之热血沸腾。</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/apple-watch-series-5/Sat, 05 Oct 2019 15:57:00 +0806入手《风之谷》漫画https://blog.imalan.cn/archives/kaze-no-tani-no-naushika-manga/<p>《风之谷》(日语:風の谷のナウシカ)是宫崎骏于 1982 至 1994 年间在杂志《Animage》上连载的漫画作品。整个故事以末日幻想为题材,架构在一个过去兴盛的文明受到毁灭,大地严重污染、遍布毒物,以及充斥着巨大异形昆虫的世界里<sup id="fn_ref_1"><a href="#fn_1">1</a></sup>。在这个世界里,「散发着毒气的森林(腐海)」不断侵蚀生活场所,人类与虫族时有摩擦,总体上生活在夹缝之中。</p> <p>关于剧情先不多说。大多数人对《风之谷》的印象来自宫崎骏监督的同名动画电影,原作得到的关注要少一些。漫画第一辑销量约五万本,动画上映后作品收到大量关注,销量激增。至连载结束前,各册合计销量约 527 万本;连载结束后漫画多次再版,至 2005 年全部累计销售 1100 万本以上。</p> <hr> <p>以上都不是重点,这次的主题是开箱新入手的漫画全辑。看了《风之谷》电影后迷上了作品里的世界观,同时被娜乌西卡全面圈粉。考虑到电影只是漫画的部分剧情,并且人物塑造、剧情等都有所简化,我决定入手原作补完整个故事。下单后等了两天,今天中午终于收到了包裹,迫不及待地拆包:</p> <p><figure style="flex: 83.33333333333333" ><img width="1920" height="1152" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/9d22cdabc5b722add974fe7dfa179003.png" /></figure></p> <p>来自台湾东贩出版社,简盒装。由于外包装非常非常严实,因此外观没有什么瑕疵。</p> <div class="photos"> <figure style="flex: 30.998851894374283" ><img width="1080" height="1742" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/68eb3102759392cd9f119119095ab004.png" /></figure> <figure style="flex: 47.285464098073554" ><img width="1080" height="1142" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/f6cfdb912495ba14d863c8a1f8d9e3dc.png" /></figure></div><p>相比起普通漫画开本要大一些,差不多是普通杂志的大小。里面每本分册也有塑封膜,拆一本看看内页吧:</p> <p><figure style="flex: 88.95985401459853" ><img width="1950" height="1096" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/28b552caefdd3955a6735ac6b9750f57.png" /></figure> <figure style="flex: 56.962025316455694" ><img width="1080" height="948" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/9d8791059a264edc33ab8331f46e6a76.png" /></figure></p> <p>印刷质量还不错。</p> <p>这也是我第一次看到宫崎骏的画风。宫老的电影作画属于比较清新的,没想到漫画画风相当繁复。当然也与故事的主题有关。现在一般认为宫崎骏主要成就来源于动画电影,漫画作品并不算多,《风之谷》则是难得的、出自宫老本人之手的长篇漫画作品。在漫画领域屡屡碰壁,再加上意识到自己的作品难以突破手冢治虫漫画设下的各种限制,作为漫画家的宫崎骏总体来讲并没有得到很高的成就。</p> <p>其实《风之谷》漫画本身在日本漫画史上的地位也算不得十分重要。但即使如此,通过漫画一窥宫崎骏早期的思想与风格还是很有意义。</p> <hr><div class="footnotes"><ol><li id="fn_1">来自维基百科,下文销量数字同。 <a no-style href="#fn_ref_1">↩</a></li></ol></div>hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/kaze-no-tani-no-naushika-manga/Tue, 15 Oct 2019 16:40:00 +0806《长日将尽》:一种慰藉https://blog.imalan.cn/archives/the-remains-of-the-day/<p>《长日将尽》这本书是在高铁上读完的。去程读了三分之一,回程一口气读完。也许列车这一物件与此书气质相合,毕竟石黑一雄正是借由史蒂文斯驾车远游途中的回忆写出了本书。</p> <p>以一位老牌英国管家的口吻叙事,让这本书趣味无穷。谨小慎微、面面俱到的语言,有时甚至显得絮絮叨叨,让隐含其中的情感无比细腻。必须认真揣摩才能抓住史蒂文斯镇定自若的叙述中流露出的真情实感;一旦抓住,则让人感触至深,甚至合卷叹息。</p> <p>虽然只是一位管家的回忆,展现的图景却相当宏大。一二战期间达林顿府往来无白丁,达官显贵是府上常客;至战后门可罗雀,落到宅邸出售给外国人的境地。历史如同车轮般滚滚向前,其间兴衰交替、人物沉浮,既体现在世界大局上,也体现在每一个小人物身上。</p> <p>诺贝尔文学奖给石黑一雄的评语是:</p> <blockquote><p>以其巨大的情感力量,发掘了隐藏在我们与世界的虚幻联系之下的深渊。</p> </blockquote> <p>以小见大是《长日将尽》的一大特色。这或许这不是石黑一雄的本意,他说本书的出发点是想书写:</p> <blockquote><p>你是如何为了成就事业而荒废了你的人生,又是如何在个人的层面上蹉跎了一辈子的。</p> </blockquote> <p>在我看来,成书真正达到的高度何止如此。书里涉及的议题非常多,大至世界如何运行,小至职务细节,都借史蒂文斯的口表达了有趣的见解。当然,他绝不会直接说出来,但是通过许多顾左右而言他的叙述,周周转转之下我们还是能揣摩一二。</p> <p>不可否认的是,这本书很大程度上是以史蒂文斯为中心的,写他的一生,以及他所经历的巨变。他对「伟大管家」的理解以及对职业精神的追求构成了他做出抉择的依据,也直接导致了他的成就与遗憾。</p> <p>坚决声称自己只是旁观者的史蒂文斯对自己以及达林顿勋爵的态度是非常复杂的。一方面坚决地维护「达林顿勋爵是一位真正的绅士」,另一方面又对爵爷亲纳粹的倾向感到不快,甚至拒绝承认自己是达林顿府管家;一方面只字不提对肯顿小姐的感情,一方面又一厢情愿地希望在若干年后请她回府上共事。这是一位非常矛盾的人,恐怕他自己也因为这些矛盾而感到困扰。</p> <p>这本书的动人之处是其细致入微的人文关怀。石黑一雄能将藏在事物背后的情感呈给读者,又做到恰到好处、绝无煽动读者的嫌疑。</p> <p>这可能就是二三流作者与一流作家的差距。总之,我为能读到这样一部一流作家的一流作品感到无比幸运。</p> <hr> <p>一件趣事。读《长日将尽》时我不禁想,这要是能由霍普金斯出演拍成电影就好了……合上书,发现书背上写着确实已经在 1993 年拍成了电影,主演也确实就是安东尼·霍普金斯。 ​</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/the-remains-of-the-day/Tue, 22 Oct 2019 22:35:00 +0806查看 GitHub Release 下载量https://blog.imalan.cn/archives/view-download-count-of-github-release/<p>有时候也会好奇某个仓库到底被下载了多少次呢……所以写了个 PHP 脚本从 GitHub API 请求数据,然后输出到终端上,大概效果如下:</p> <p><figure style="flex: 75.0814332247557" ><img width="1844" height="1228" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/e53c5f341e4e1da3367560f869376ef8.png" /></figure></p> <p>使用方法:去 <a href="https://gist.github.com/AlanDecode/29f3e5b876d9ea03b1dc5c2fba8ef808">AlanDecode/getDownloadInfo.php</a> 获取代码保存为 getDownloadInfo.php,并修改里面的 <code>$UserName</code> 和 <code>$RepoName</code> 两项为你感兴趣的值,然后在命令行中运行 <code>php getDownloadInfo.php</code> 即可。PHP 需要启用 curl 扩展。</p> <p>受 API 限制,Source ​code 和直接下载仓库的无法统计,只能统计 Release 中自己添加的附件。看看 VOID 主题发布版的下载量吧:</p> <p><figure style="flex: 75.0814332247557" ><img width="1844" height="1228" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/5a8437dbfa9ec35a49138d2196fa4031.png" /></figure></p> <p>啊,还真是少得可怜啊……</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/view-download-count-of-github-release/Sun, 27 Oct 2019 17:40:00 +0806Maverick - Go My Own Way.https://blog.imalan.cn/archives/blog-now-powered-by-maverick/<p>首先是在少数派上写一些折腾经验,然后是自己基于 WordPress、Typecho 建站,我在各种网络平台上涂鸦也快三年了。这期间尝试了许多写作、建站工具,深感该领域百花齐放,令人眼花缭乱。很长的一段时间里,我沉浸于给博客程序写主题写插件、使用诸如 CDN 等方式优化网站速度、研究 SEO 玄学,不亦乐乎。</p> <p>但终究有些厌倦了。Typecho 这类动态博客程序固然方便,然而自己管理一个服务器确实是负担,而且对图片、内容的管理不甚自由,一旦需要迁移就是噩梦一场;若只是在第三方平台上发表东西,就总觉得互联网上少了属于自己的一亩三分地。</p> <p>因此我曾几次把目光转向静态博客。诸如 Hexo、Jekyll 这类静态博客生成器让用户在本地编写 Markdown 文本,再由生成器完成从原始文件到博客网页的转换。某种程度上,这类程序让写作者从维护网站的繁琐中解脱出来,而专注创作本身。MWeb、Gridea 作为博客写作工具时也属于此类。</p> <p>然而实践下来,仍然有两个问题没能得到解决:</p> <ol> <li>博客源文件的管理问题。想要达到真正的内容与展示分离,就不应该限制源文件的存储方式。现在的生成器要么要求把内容存储到自己的库中,要么要求放在生成器目录下的特定位置,某些甚至对目录结构都有要求。这并不是我想要的;</li> <li>图片等静态资源的处理问题。Markdown 对图片的处理一直都是痛点。我不希望把图片放在任何第三方图床,且不提上传图片-获得外链-插入外链这个过程多么不便,远程图床还有随时跑路的风险;而现在的生成器又不能很好地处理本地图片,至少处理起来并不优雅。</li> </ol> <p>Alan Key 的一句名言,被 Steve Jobs 引用后众人皆知:</p> <blockquote><p>People who are really serious about software should make their own hardware.</p> </blockquote> <p>拿来活用,则「任何认真对待博客的人都应该自己写博客系统」。这话说起来固然偏执,但确实为吹毛求疵的人指了一条明路。既然现在的轮子都不满意,那就应该造自己的轮子。本着学习与探索的目的,我使用 Python 自己写了一个静态博客生成器,取名 <strong>Maverick</strong>,开源于 GitHub。它现在正驱动着我的<a href="https://blog.imalan.cn/">个人博客</a>与我的<a href="https://wiki.imalan.cn/">个人 Wiki</a>。</p> <p><figure style="flex: 166.88102893890675" ><img width="1038" height="311" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/afc22a324a16fd544bd1e6c4393cde60.png" /></figure></p> <p><a href="https://github.com/AlanDecode/Maverick"><strong>项目主页</strong></a> | <a href="https://blog.imalan.cn/"><strong>个人博客</strong></a> | <a href="https://alandecode.github.io/Maverick/"><strong>演示站点</strong></a> | <strong>欢迎反馈与Star</strong></p> <p>现在可以说说 Maverick 本身。大致上,它与 Hexo 等相似,都是通过解析 Markdown 文件生成网页。不过我在设计它时考虑到了上文所述的系列问题,并做了针对性的改进。</p> <p>首先是 Maverick 对源文件的处理。Maverick <strong>不限制</strong>源文件的存储位置,你可以把文章目录放在电脑上的任何路径下,例如 Dropbox、iCloud Drive,以便备份、同步、版本管理,以及在任何设备上用任何编辑器写作。Maverick 也<strong>不限制</strong>源文件的组织结构,你可以按照你喜欢的方式组织它们,按时间、按类别都可以。</p> <p>为了达到这一点,Maverick 通过叫做 <code>source_dir</code> 的选项在指定路径下搜索所有 Markdown 文件,并根据里面提供的信息将它们分门别类,生成日期标签等等。这些内容被称作 <code>frontmatter</code>,也就是使用 Hexo 等写文章时顶部的使用 <code>---</code> 包裹起来的那部分东西。这样的设计让以前的内容可以被复用,也便于以后的迁移。</p> <p>此外是对图片的处理。Maverick 允许在 Markdown 文件中引用<strong>任何位置</strong>的图片,并且都能在生成网站时合适地处理它们。若你在原始文本中通过绝对路径或者相对路径引用本地图片,Maverick 会在生成网站时自动寻找它们,并把它们复制到统一的位置,同时修改文章里的引用链接;若通过 URL 引用了远程图片,则(可选地)将它们下载到本地缓存,按本地图片对待。这样处理的好处很多。</p> <ul> <li>Typora、VS Code 等软件都支持插入与预览本地图片。尤其是 Typora,本身还提供了插入图片时自动将图片复制到相对文章的某个目录的功能,这与 Maverick 的处理方式十分契合;</li> <li>通过本地缓存,减小了图片丢失的风险。若图床跑路,你还可以在本地找到一份备份,不会影响你发布的内容;</li> <li>虽然目前还没有实现这个功能,但通过本地化以及统一的发布流程,可以实现自动图片处理(例如压缩);</li> <li>可以提前获知图片的大小尺寸等信息,并在网页展示时提供优化的体验(比如图片排版与点击放大)。</li> </ul> <p>而这一切都发生在生成站点时,不会对原本的文章有任何影响,不需要在文章里多加什么标注或者声明。只需要用标准的 Markdown 语法引入图片就好。此外,不论是缓存还是尺寸信息,Maverick 都会在生成时缓存下来,不会反反复复地请求与解析。</p> <hr> <p>除了以上两点改进,Maverick 还自带了一些博客的常用功能,例如 RSS 源生成、<strong>实时搜索</strong>、Sitemap 等。这一切你都可以在<a href="https://blog.imalan.cn">我的博客</a>以及<a href="https://alandecode.github.io/Maverick/">示例站点</a>上体验到。目前,Maverick 没有插件机制,但以我自己的体验而言,应该具备了个人博客应有的功能。</p> <p>我在 Lepture 开发的 Markdown 解析器 <a href="https://github.com/lepture/mistune">mistune</a> 基础上进行扩展,添加了一些 Markdown 语法,使之能够良好地支持代码高亮、行内脚注、数学公式、图片排版等。</p> <p>网页端的展示方面,Maverick 使用自带的主题 Galileo。这是一个比较简洁的主题,以文字阅读体验为重心开发,样式上借鉴了<a href="https://anyway.fm/post/">安妮薇日报</a>与 <a href="https://github.com/probberechts/hexo-theme-cactus">hexo-theme-cactus</a> 的设计。文字排版效果请参见 <a href="https://alandecode.github.io/Maverick/archives/typography/">Typography - Maverick</a>。</p> <hr> <p>总之,我从自己的观察与需求出发,自己写了一款静态博客生成器。本文只覆盖了关于 Maverick 的很小的一部分,欢迎各位移步 <a href="https://github.com/AlanDecode/Maverick">项目主页</a> 阅读完整的说明。</p> <p>这是我用 Python 写的第一个像点样的东西,因此不免有些遗漏与错误,任何建议与反馈都十分欢迎。当然 Star 则是特别欢迎😜。</p> <p>那么,就这样,感谢各位阅读。周末愉快~</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/blog-now-powered-by-maverick/Fri, 13 Dec 2019 11:27:00 +0806完全使用 GitHub 写博客https://blog.imalan.cn/archives/blog-with-github/<p><figure style="flex: 100.0" ><img width="1420" height="710" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/13452d991bfec0ed426cd0615bc53703.png" /></figure></p> <div class="notice">注意:这不是所谓使用 Maverick 的「标准方法」,只不过是利用 Maverick 与 GitHub Actions 写博客一个流程而已。不要觉得非得这样不可。Maverick 的用法见 <a href="https://github.com/AlanDecode/Maverick">README.md</a>。</div><p>这篇文章分享我目前的博客工作流:基于 GitHub 与 <a href="https://github.com/AlanDecode/Maverick">Maverick</a> 获得一站式的博客体验。借助这个自动化流程,能够达到:</p> <ul> <li>超酷超方便的图片管理(当然了,Maverick 嘛)</li> <li>博客版本管理(当然了,GitHub 嘛)</li> <li>在任何设备上写博客,包括浏览器(当然了,GitHub 嘛)</li> <li>无需值守的自动化构建(当然了,GitHub 嘛)</li> <li><strong>免费、超快的全球 CDN!</strong>(当然了……jsDelivr 嘛!)</li> <li>不需要在本地安装什么第三方程序(不过要在本地写文章的话,Git 还是需要安装一下)</li> </ul> <p>目前,本博客与 <a href="https://wiki.imalan.cn">无知识</a> 都跑在这个流程上。这篇文章在 iPad 上写成,内容与发布部分并没有碰过代码,也没有开过终端。虽然本文的工作流是针对 Maverick 优化的,但理论上它适用于任何可以托管在 GitHub Pages 的静态博客。只不过若要完全达到本文效果还需要进一步调教。请多关注原理。</p> <div class="notice">用以保存与构建本博客的仓库是 <a href="https://github.com/AlanDecode/site-Blog">Alandecode/site-Blog</a>。为了方便各位自己尝试,我还创建了一个<a href="https://github.com/AlanDecode/Blog-With-GitHub-Boilerplate">示例仓库</a>,请各位把这个仓库 fork 到自己那里,然后按照 README.md 的步骤完成一遍,就知道整个流程到底是怎样的了。本文更多的还是关注原理与细节。</div><h2>为什么要用 GitHub 管理博客源文件</h2> <p><strong>安全、持久以及版本管理</strong>。许多写博客的人不重视这一步,总爱把源文件放在电脑上某个随意的角落,或者直接发布到博客程序中而不加备份。依我看,这都是没有远见的做法。一旦坚持得久了,老旧的文章就是一笔财富;但电脑会坏,服务器可能被误操作删库,这都会造成无法挽回的损失。除源文件(专指文本内容)外,对图片等附件的管理也有这个问题,并且更加棘手。</p> <p>更好的实践是把源文件放在诸如 Dropbox、iCloud 之类带有版本管理的服务里,以减少丢失的可能;当然,也包括 GitHub。<strong>最好不要</strong>把图片等附件托管到任何在线服务并在源文件中引用,而应该保持它们与源文件位于同处。也就是说,你的所有内容应该是自给自足的,而不依赖第三方的服务。</p> <blockquote><p>你其实完全没有必要使用第三方图床引用图片。大多数的编辑器都支持通过相对路径或者绝对路径引用与预览本地图片,甚至包括 GitHub。你可以在浏览器中打开这篇文章存放在 GitHub 上的 <a href="https://github.com/AlanDecode/site-Blog/blob/master/src/%E5%81%B6%E5%B0%94Geek/2019-12-17-%E5%AE%8C%E5%85%A8%E4%BD%BF%E7%94%A8-GitHub-%E5%86%99%E5%8D%9A%E5%AE%A2.md">Markdown 原文</a>,可见所有通过类似 <code>./assets/img.png</code> 的链接引入的图片都得到了正确的展示。</p> </blockquote> <p>GitHub/git 天生适合这个任务。只需要新建一个仓库,把所有的文本与图片都放在里面,增删文件时提交更改并推送到 GitHub,借助不限量、不限时的版本回溯,你的内容已经相当安全了。此外,GitHub 私人仓库目前已经免费,虽然每个仓库有 1G 的容量限制,但相信我,1G 足够个人博客使用。</p> <p><strong>GitHub 本身就是具有网页版编辑界面的内容管理系统</strong>。与其大费周章地自己构建一套 Web 写作前端,何不使用 GitHub 现成的呢?国际大厂,值得信赖;<strong>真正</strong>的 GitHub-flavored Markdown 风格;而且还有大量的第三方软件可以同步 GitHub 仓库进行编辑。</p> <p>另外,使用 GitHub 管理源文件是本文所述流程的基础。</p> <h2>如何在 GitHub 上托管一个网站</h2> <p>虽然许多人知道 Hexo 可以把网站「发布到 GitHub Pages 上」,但由于 App 的兴起,多数人都对「网站」这个概念有些模糊。这里,我简要地介绍到底什么是静态网站、网页,以及 GitHub Pages 到底是什么,它在背后做了些什么。</p> <p>这里,我们只讨论最传统的网页。最传统的网页都是静态网页,在服务器上就是一个个 HTML 文件。当通过「链接」访问这个服务器时,服务器就根据链接来找到对应的 HTML 文件,把里面的内容传送到浏览器,浏览器再把内容渲染与展示到你面前。</p> <p>举个例子,你通过 <code>https://test.com/index.html</code> 访问时,<code>test.com</code> 这个服务器就把位于网站根目录下的 <code>index.html</code> 文件内容发送给浏览器,让浏览器展示。由于多数网站都采用 <code>index.html</code> 这个文件名,所以很多时候就可以省略不写,直接访问 <code>https://test.com/</code> 也能获得相同的效果。</p> <p>GitHub Pages 在这里其实就充当了 <code>test.com</code> 这个角色,它是一个服务器。</p> <p>我们说「发布到 GitHub Pages 上」,其实指的仍然是把文件上传到某个 GitHub 仓库中,只是这里的文件就不是代码或者源文件,而是<strong>被生成的,要交给浏览器展示的 HTML 文件</strong>,为对应的仓库开启 Pages 服务以后,GitHub 就能在收到访问请求时把仓库里的 HTML 文件发送给浏览器展示。</p> <p>目前,GitHub 上的<strong>所有仓库</strong>都可以开启 Pages 服务,网络流传的所谓「只能启用一个」是错误的。仓库分为两类:</p> <p>第一类,仓库名形如 <code>&lt;用户名&gt;.github.io</code>。GitHub 会默认为这类仓库开启 Pages 服务,可以直接通过 <code>http://&lt;用户名&gt;.github.io</code> 访问。</p> <p>第二类,其它任何名称的仓库。对这些仓库,Pages 服务可以在仓库设置中手动打开,并通过 <code>http://&lt;用户名&gt;.github.io/&lt;仓库名&gt;</code> 访问。</p> <p>两类仓库都可以指定部署的内容来源,包括:</p> <ul> <li>master 分支(默认)</li> <li>master 分支中的 docs 文件夹</li> <li>gh-pages 分支</li> </ul> <p>这两类仓库都可以绑定自定义域名,方法相同,在仓库中创建 <code>CNAME</code> 文件或者在设置中绑定就行。此外,<strong>私有仓库也可以开启 Pages 服务</strong>,这十分适合用来发布博客,设想在 master 分支中存储源文件,是只自己可见的;将生成的网站发布到 gh-pages 分支,是公众可见的。这是兼具安全与便捷性的方案。</p> <h2>如何使用 GitHub 自动构建与发布博客</h2> <p>已经有很多人摸索出了利用 GitHub 托管与自动构建的流程,例如我曾写过的 <a href="https://blog.imalan.cn/archives/213/">使用 Travis CI 自动生成与部署 Hexo 博客</a> 就能针对 Hexo 博客达到该目的。这些方法本质上是一个这样的流程:当更新文章并推送到 GitHub 时,自动通过 CI 服务发起一次构建任务,然后把生成的静态博客推送回 GitHub 上。</p> <p><figure style="flex: 94.69820554649266" ><img width="1161" height="613" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/ff2fd03960807e5d485d0771bd9f63f6.jpg" /><figcaption>使用 GitHub 与 Travis CI 自动构建</figcaption></figure></p> <p>但是,Hexo 等生成器大多要求将博客源文件存放在某个特定的目录下,让我们无法自由地组织源文件;另外,这些生成器对图片的处理十分笨拙,不能应对引用本地文件的情景;当然,它们也无法达到本文稍后要谈到的 CDN 加速效果。</p> <p>开源的 <a href="https://github.com/AlanDecode/Maverick">Maverick</a> 是一个与 Hexo 类似的博客生成器,不同的是它针对源文件与图片管理做了大幅改进,可以从任何目录寻找源文件,并且可以自动处理引用本地图片的情况。那么,如何将它结合到流程中呢?</p> <p>这里,我想用啰嗦一点的方式讲解,并且穿插介绍一些技术上的基本概念。别怕,不难。</p> <h3>需求梳理</h3> <p>为了有的放矢地进行设计,我们先明确需求。大致上,要达到以下目的:</p> <ul> <li>使用 GitHub 管理自己的博客源文件(一堆 Markdown 文本文件),以及文件中引用的图片</li> <li>在更新内容时,例如增、删、改博客文章,希望 GitHub 能察觉到并自动更新托管在 GitHub Pages 的博客网站</li> <li>静态博客生成器与博客源文件应该分离开来,并且同样能够被版本管理,以及方便地更新。</li> </ul> <p>第一条比较简单,之前已经谈过了。现在先来说第二条。</p> <h3>基于 GitHub Actions 的自动构建</h3> <p>其实,监视仓库并在有改动时自动执行一系列动作是非常广泛的需求。软件开发工作中,经常需要对新添加的代码随时进行测试,或者进行部署,都是通过这样的自动化流程实现的。实际上这类服务还有个<ruby>专门<rp>(</rp><rt>莫名其妙</rt><rp>)</rp></ruby>的名字:「持续集成(Continuous Integration)」,简称 CI。</p> <p><figure style="flex: 90.6" ><img width="1812" height="1000" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/05105f3f45d95225059ddc5c347161ff.jpg" /><figcaption>基于 GitHub Actions 的自动构建与发布</figcaption></figure></p> <p>此前,最广泛使用的 CI 服务当属 <a href="https://travis-ci.org">Travis CI</a>;现在 GitHub 也推出了自家的 CI 服务 <a href="https://github.com/features/actions">GitHub Actions</a>。相比起 Travis 来,GitHub Actions 更加 「native」,经我测试似乎也更敏捷、快速;此外,还能直接引用别人写好的规则。本文就基于 GitHub Action 描述构建流程。</p> <p>当仓库收到新的更新(push)时,GitHub 会根据仓库中 <code>.github/workflows</code> 文件夹下的 YML 配置文件启动 CI 流程。一个简单的 YML 文件长这样:</p> <div class="highlight"><pre><span></span><span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">Build</span> <span class="c1"># 指定只在 master 分支的 push 事件发生时执行</span> <span class="nt">on</span><span class="p">:</span> <span class="nt">push</span><span class="p">:</span> <span class="nt">branches</span><span class="p">:</span> <span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">master</span> <span class="nt">jobs</span><span class="p">:</span> <span class="nt">build</span><span class="p">:</span> <span class="nt">runs-on</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">ubuntu-latest</span> <span class="nt">steps</span><span class="p">:</span> <span class="c1"># 首先拉取最新的代码,这里直接使用已有的规则</span> <span class="p p-Indicator">-</span> <span class="nt">uses</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">actions/checkout@v2</span> <span class="c1"># 安装生成环境,也是已有的规则</span> <span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">Set up Python 3.7</span> <span class="nt">uses</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">actions/setup-python@v1</span> <span class="nt">with</span><span class="p">:</span> <span class="nt">python-version</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">3.7</span> <span class="c1"># 安装依赖包</span> <span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">Install Deps</span> <span class="nt">run</span><span class="p">:</span> <span class="p p-Indicator">|</span> <span class="no">pip install -r requirements.txt</span> <span class="c1"># 进行构建</span> <span class="c1"># 这一步需要根据实际的构建方式修改</span> <span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">Build</span> <span class="nt">run</span><span class="p">:</span> <span class="p p-Indicator">|</span> <span class="no">make all</span> <span class="c1"># 把构建的结果推送到 gh-pages 分支</span> <span class="c1"># 这里也直接使用别人写好的规则,只需要自定义几个设置项(env 与 with)</span> <span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">Deploy to GitHub Pages</span> <span class="nt">uses</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">docker://peaceiris/gh-pages:v2</span> <span class="nt">env</span><span class="p">:</span> <span class="c1"># 注意这里的 PERSONAL_TOKEN,由于默认的 GITHUB_TOKEN 无法触发</span> <span class="c1"># 公共仓库的 Pages 构建,因此需要在账户设置中生成 TOKEN,并添加到</span> <span class="c1"># 本仓库的 secrets 里</span> <span class="nt">PERSONAL_TOKEN</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">${{ secrets.PERSONAL_TOKEN }}</span> <span class="nt">PUBLISH_BRANCH</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">gh-pages</span> <span class="nt">PUBLISH_DIR</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">./dist</span> <span class="nt">with</span><span class="p">:</span> <span class="nt">emptyCommits</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">false</span> </pre></div> <p>上面这个示例文件在 master 分支收到更改时做了这几件事:</p> <ol> <li>拉取最新的代码</li> <li>安装环境,例如 Hexo 所需的 Node,或者 Maverick 所需的 Python 等</li> <li>安装依赖包</li> <li>执行构建</li> <li>把构建结果推送回 gh-pages 分支</li> </ol> <p>虽然后文我们要对这个流程进行一定修改,但大框架并不会变。可见,通过 GitHub Actions,在增删、修改文章时自动更新网站是可行的,只需要编写对应的配置文件即可。</p> <p>考虑到普适性,这里我就不展开细说这个配置文件的写法。许多静态博客生成器都是通过命令行来生成网站的,因此其实在 Build 这一步中填入对应的命令即可。</p> <h3>将生成器纳入版本管理</h3> <p>将生成器一并纳入版本管理不是必须的,因为我们总可以在生成站点时临时安装最新版的生成器。然而这样一来有几处不便:</p> <ol> <li>无法控制要使用的生成器版本。我们当然不一定总是想要用最新版,特别是考虑到有时候生成器本身升级可能导致一些不兼容的问题</li> <li>不能在升级生成器的时候触发网站更新。由于生成器并不在我们的版本管理系统中,若博客内容没有修改,即使生成器更新了也无法触发相应的网站更新</li> </ol> <p>Git 子模块(submodule)是解决以上问题的直接途径。Git 子模块专门用于处理一个项目依赖于别的项目的情况,在本文,也就是博客这个项目依赖于生成器项目。生成器作为子模块被引入博客项目中。</p> <p>这里简要介绍添加、更新、管理子模块的方法。假设现有一仓库,名叫 <code>ParentProject</code>,作为父项目,我们要在这个仓库里引入 Maverick 作为子模块:</p> <div class="highlight"><pre><span></span><span class="nb">cd</span> ParentProject git submodule add https://github.com/AlanDecode/Maverick.git git add . git commit -m <span class="s2">&quot;add submodule Maverick&quot;</span> </pre></div> <p>此时 Maverick 就会出现在 <code>ParentProject</code> 文件夹下,并纳入父项目的版本管理。注意,这时候子模块的版本就<strong>锁定</strong>在了添加它时的那次提交,如果子模块有更新,这边要怎同步更新呢?</p> <div class="highlight"><pre><span></span><span class="nb">cd</span> ParentProject/Maverick git pull --rebase <span class="nb">cd</span> .. git add . git commit -m <span class="s2">&quot;update submodule Maverick&quot;</span> </pre></div> <p>也就是:先进入子模块文件夹,拉取新的提交,然后切换回父项目,提交更改。这样一来父项目中的 Maverick 更新到了最新的一次提交。</p> <p>这样做的好处很明显。由于子项目只是作为一个引用出现在父项目中,可以很方便地升级降级,而且不会与父项目的文件混淆。这个技巧其实可以用在许多的生成器上,只要生成器本身是使用 Git 开源的。</p> <h3>把这些步骤串联起来!</h3> <p>到现在为止我们已经将需求点各个击破,但似乎有点「只见树木不见森林」。如何基于上面的各个技巧构建一套流程呢?</p> <div class="notice">再次提醒,为了方便各位自己尝试,我创建了一个<a href="https://github.com/AlanDecode/Blog-With-GitHub-Boilerplate">示例仓库</a>,请各位把这个仓库 fork 到自己那里,然后按照 README.md 的步骤完成一遍,就知道整个流程到底是怎样的了。本文更多的还是关注原理,而不是手把手教程。</div><p>现在,我们已经有了一个仓库,里面存放了我们的博客源文件以及图片等等,假设这些都放在 <code>src</code> 文件夹下;以及通过子模块引入了 Maverick,放在 <code>Maverick</code> 文件夹下。此外,还有针对站点的设置文件 <code>conf.py</code> 放在仓库根目录下。当这个仓库收到任何更改,不论是修改源文件,还是更新子模块,还是修改配置文件,GitHub 都能探测到并根据预先设定的规则执行任务:启动一次构建,构建源文件是仓库 <code>src</code> 文件夹下的内容,配置文件是仓库根目录下的 <code>conf.py</code> 文件,生成的结果要推送回仓库的 gh-pages 分支。</p> <p>应该相当明了吧。这个构建过程所使用的 GitHub Actions 文件,请参考 <a href="https://github.com/AlanDecode/site-Blog/blob/master/.github/workflows/ci.yml">ci.yml</a>。</p> <p>接下来聊聊 CDN 的问题。</p> <h2>如何自动加上 CDN 支持</h2> <p>不,我说的当然不是 CloudFlare。</p> <p>而是 jsDelivr。jsDelivr 是一家全球 CDN 服务商,特别值得一提的是,在官网上它宣称</p> <blockquote><p>jsDelivr is the only public CDN with a valid ICP license issued by the Chinese government, and hundreds of locations directly in Mainland China.</p> </blockquote> <p>它的速度在中国相当不错。而且还有一个重要特性:支持加速来自 GitHub 仓库的文件!只要构造一个类似这样的 URL:</p> <pre><code>https://cdn.jsdelivr.net/gh/&lt;用户名&gt;/&lt;仓库名&gt;@&lt;分支名&gt;/&lt;文件路径&gt;</code></pre> <p>例如:</p> <pre><code>https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/favicon.ico</code></pre> <p>就能直接访问对应文件。这一点可以被我们加以利用,用以加速博客上的 CSS、JS、图片等静态文件。</p> <p>要想实现这一点,不免要求生成器自身能够处理这些链接。Maverick <strong>自带了这个功能</strong>。它可以自动为博客中引用的本地图片加上 CDN 支持,同样也支持 CSS、JS 等文件。</p> <p>Maverick 所做的,就是根据设置的用户名、仓库、分支生成对应的链接,并在生成网站时替换原来的链接。我在文首倡导将图片放在本地也有这个原因:使生成统一的 URL 成为可能。</p> <p>对了,使用这个方法要求仓库是公开的,否则 jsDelivr 无法获取要加速的文件。</p> <h2>结语</h2> <p>折腾出这一套流程工作量不小。考虑到我还专门自己写了一个生成器(就是 Maverick),这其实是相当庞大的工程。</p> <p>虽然过程曲折繁复,但最终的结果却是简约的,甚至可称得上傻瓜式。这一切都是为了让写博客再轻松、方便一点。</p> <p>我猜许多人看到这么多字就已经不耐烦了。但是无妨,你只要知道一点:我把这个文章的成果总结成了一个<a href="https://github.com/AlanDecode/Blog-With-GitHub-Boilerplate">仓库</a>,这是一个可以立即上手的模板,你只需要把它 fork 到你的账户下,然后根据 README.md 里的步骤操作一遍就知道该怎么用了。请务必一试。</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/blog-with-github/Tue, 17 Dec 2019 20:34:00 +0806Kepler:用 Maverick 方便地写 Wikihttps://blog.imalan.cn/archives/wiki-with-maverick-and-present-by-kepler/<p>前段时间我在少数派上发了一篇<a href="https://sspai.com/post/58013">文章</a>介绍 Maverick 以及基于 Maverick 利用 GitHub Actions 自动发布静态博客的流程,引来了不少踊跃的实践者。其中很多都问我 Maverick 能不能换主题?</p> <p>能,确实是能;但是现在选择比较少。Maverick 自带了一款主题 Galileo,现在还有 <a href="https://www.velasx.com/">Zeee</a> 写的主题 <a href="https://github.com/Reedo0910/Maverick-Theme-Prism">Prism</a>。</p> <p>Maverick 问世后,我就把个人博客与个人 Wiki 都迁移到了这个自主知识产权的生成器上。个人博客体验尚可;但是 Wiki 嘛……Galileo 显然不是针对 Wiki 设计的主题。因此我牺牲宝贵的工作时间,大胆摸鱼,现在就向大家呈现针对个人 Wiki 设计(照抄 GitBook)的新主题:Kepler。请前往<a href="https://wiki.imalan.cn/">我的 Wiki</a> 查看效果。</p> <div class="notice">Maverick 现已内置 Kepler,请升级 Maverick 为新版本,并在站点配置文件中修改 <code>template="Kepler"</code>。</div><div class="photos"> <figure style="flex: 72.76381909547739" ><img width="1448" height="995" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/37f6dd70f1d1a6fd983e9a7c25eb9e80.png" /></figure> <figure style="flex: 72.76381909547739" ><img width="1448" height="995" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/58261802ee96b37295053b9e91175bf3.png" /></figure></div><p>Kepler 的设计来自 GitBook,也带有便利的侧边栏、文章目录、全站搜索。此外 Kepler 支持 PJAX 全站无刷新,浏览体验无比流畅;得益于 PJAX,站点成为了一个单页应用,且对 SEO 友好。</p> <div class="photos"> <figure style="flex: 26.616541353383457" ><img width="354" height="665" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/6b78e502e1080101c113a618b09c34a8.png" /></figure> <figure style="flex: 27.092198581560282" ><img width="382" height="705" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/bbc676ee551d52695e869f76049d91cf.png" /></figure> <figure style="flex: 27.092198581560282" ><img width="382" height="705" src="https://cdn.jsdelivr.net/gh/AlanDecode/site-Blog@gh-pages/archives/assets/74cd4b857590b36f71776e5dd61f6fd6.png" /></figure></div><p>Kepler 是响应式的,在平板、手机等小屏幕上均有良好体验。</p> <hr> <p>我自己在不断探索个人知识体系的构建方法。最近几年笔记工具领域新秀层出不穷,目前最火的当属 Notion。但是我一直用不惯 Notion,它推崇的「模块化」笔记方法在我这里 makes no sense。因此看到现在印象笔记、GitBook 都开始照搬 Notion,心中感到遗憾。</p> <p>一年半之前写下的 <a href="https://blog.imalan.cn/archives/108/">为什么每个人都应该有自己的 Wiki</a> 中我对个人 Wiki 的期待现在也没有改变,最重要的无非两点:分类,搜索。在 Maverick 之前,我使用 Hexo 发布个人 Wiki,那时使用的主题 <a href="https://github.com/zthxxx/hexo-theme-Wikitten">Wikitten</a> 各方面都相对不错。这次写 Kepler 时把 Wikitten 最核心的两点都移植过来了:侧边栏分类树以及全站搜索,并且在展现方式上仿照 GitBook 进行了大幅增强。此外增加了更实用的文章目录。</p> <p>对了,关于文章分类有一点值得一提,Maverick 最近的版本中增加了 <code>category_by_folder</code> 选项,也就是可以根据文件目录结构确定文章分类,不用纠结于 <code>front-matter</code>。这个功能搭配 Kepler,我认为体验甚至优于 GitBook(个人自吹,不要试图在评论区纠正我),希望你们能喜欢。</p> <p>那么,就到这里。新年快乐。</p> hi@imalan.cn (熊猫小A)https://blog.imalan.cn/archives/wiki-with-maverick-and-present-by-kepler/Thu, 02 Jan 2020 16:21:00 +0806