我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。

精英盒子的黑历史

2009

2009 年 9 月,精子出于自己用 VB6 做一款叫「精英农场」的山寨游戏的目的,组建了后来被称为「精英盒子」的 QQ 群。当时精子已经在网络上发布过一些作品了,第一批成员就是这些小作品的用户,如 @PCLiker. 这个群从建立的一开始,就有着变态,复杂,冗长,独裁的群规,作为传统一直流传了下去,虽然有时执行得严格,有时疏于执行。

2010

2010 年 1 月,「精英农场」完成了一半,精子去百度贴吧发了两个 宣传贴,在之后的接近三年时间里,这个帖子源源不断地拉进来了上百人,@老易 和 @谦谦 是其中之一。

4 月份的的时候,本来计划将「精英农场」改为网络版,但在精子学习了一些网页制作,和 Web 后端编程技术后,精子觉得建网站更有意思。

精子的第一个网站域名是 jingyingbox.cn, 其实也就是用别人写的程序,挂上去一些自己写的有关计算机技巧的一些文章。

在 2010 下半年,精子调研了 PHP 和 ASP 之后,决定用 .Net 编写自己的网站,并于 10 月完成了 第一版,起名 JyNet 并换成了 jybox.net 这个域名。

2011

在 2011 年 6 月,精子参加了中考,在此之前的三个月里,精子花了三个月的时间来准备中考,期间委托 @黑龙 监督是不是又偷懒上了 QQ.

精子觉得自己的初中三年过得很不寻常,因此写了一篇 两万余字的长文 来记录自己初中三年发生的故事。

在中考之后,受社交网络上转发的段子影响,精子觉得腾讯是一家黑心公司,发誓以后不用 QQ. 后来精子对腾讯由黑转粉,不过因为这个变故,精子换了一个 QQ 号。

精子也觉得微软是一家黑心公司,因此决定把网站从 .Net 改成 PHP, 但挣扎了几个月之后失败了,于是还是选择了现成的 PHPWind 和 WordPress 来 改版网站。那个失败的版本,经过若干次重构,变成后来的 LightPHP.

从 2011 年 9 月开始,精子开始用 C++ 和 Qt 编写 零毫秒 ——一个即时通讯软件。连 PHP 都驾驭不了的精子,居然写 C++ 写得很开心,并在年末完成了一个简单的版本。

在 2011 年,盒子群里的核心人物是 @Abreto 和 @OUTMAN, 大家都还年轻,想得多做得少,每天就聚在一起在 QQ 群和论坛上 瞎扯淡。年末,@whtsky 加入了盒子群,@whtsky 是一个很厉害的人,让精子知道了 Python, Github, V2EX 等等很多高大上的东西,也有很多人追随 @whtsky 而来,比如 @potato.

2012

2012 年初,精子因为从微博上点到了一个 有趣的帖子 知道了果壳,然后在果壳混了大概一年的时间,期间 认识 了至今依旧呆萌的 @cry 姐姐;@cry 姐姐给精子起了很多个名字,但精子只喜欢「精子」这个名字。

年初 @whtsky 用 Python 写了一个论坛系统,精子觉得很有趣,也想一起帮忙,在此期间学习到了一些 Python Web 开发的技巧,后来用在了重构 LightPHP 中。

精子给这个论坛系统起名为 PBB, 并在 3 月份决定用它代替盒子论坛。在 PBB 上线之后,因为 @whtsky 维护不及时,问题不断,导致盒子论坛从此元气大伤。尽管后来精子把程序换成了 esoTalk, 但也 无力回天 了。因为这件事情,精子对 @whtsky 很不满意,想必 @whtsky 也是如此,后来他从盒子拉走了很多精子的粉丝,所以精子对他更不爽了。

2012 年 3 月,为了让网站更加稳定,精子决定改用 Linode, 在当时这是一笔不小的开销,因此精子决定通过出售虚拟主机的方式来回收一部分成本,因此「神马终端」诞生了。

很意外地,神马终端竟然很受欢迎,以至于手动管理用户的网站有些吃力。于是 9 月份开始,精子动手写一个虚拟主机面板,名叫 RootPanel, 最后终于在年末 上线 了,改名成了 RP 主机.

2013

2013 上半年,精子开始系统地学习计算机科学,每天在学校躲在后排看书,这半年中,精子把 深入理解计算机系统计算机网络C++ Primer Plus 看了个大概。

精子的朋友中有一些黑阔,比如 @limit, @Evi1m0, 因此在一个叫「邪红色」的黑阔组织成立之初,精子就被拉了进去,在其中混了半年,精子发现自己并 不喜欢这群人,之后分歧越来越大,和 @ricter 吵翻是后来的事情了。

2013 年比特币火了一把,价格涨得实在凶猛,精子从很早就关注比特币了,也持有了不少,因此前前后后从中 小赚了几千元钱

2013 年 6 月,RP 主机的第二个版本发布了,精子在 V2EX 发了一个 广告贴, 因此又有一大批成员加入了盒子群,包括 @qwe7002 和 @faceair 等。

2013 年 7 月,精子 遇到了 @孙亮,加入了一个叫 番茄土豆 的团队,精子发现世界好小,团队里差不多有一半的人都曾是精子的粉丝。从此精子有了稳定的收入,精子不喜欢那些不承认自己是土豪的大土豪,因此从此精子便大大方方地承认自己是一个「小土豪」。

在 2013 年的下半年,小璐 开始出现在精子的生活中,亦活跃在盒子群里,这也是盒子群最热闹的一段时间。因为小璐的关系,精子在这半年中写了很多精彩的 日志

在 2013 的年末,因为精子觉得盒子群红红火火,所以去 V2EX 上发了一个 广告贴,吹得非常离谱。因此盒子群又进来了非常一大批的人,可惜留下的并不多。从时间上来讲,这个事件过后,盒子群就变冷清了,精子也拿不准这两件事是否有因果关系。

2014

精子一直对中国的教育很是不满,在 2014 年初,精子再一次 修正自己的观点,并决定在高考前的最后一学期离开学校,至于在程序上到底算不算退学精子也不是很清楚,不过他很喜欢「高中退学」这个经历。于是精子从沈阳来到了苏州,精子很喜欢苏州的气候,也很喜欢自由的生活。

在这一年里精子见到了很多相识已久朋友,比如 @orzfly, @starriv, @mason, @lyp 还有 @cry 姐姐。

在 2014 年下半年,精子觉得反正盒子群已经这么冷清了,不如索性改成「精子粉丝团」。盒子论坛也被关掉了,变成了一个粉丝团主页。

RootPanel 0.8 版本发布:基于 Node.js 的虚拟服务销售系统

RootPanel on Github: https://github.com/jysperm/RootPanel

RootPanel 是什么

简单来说 RootPanel 是一个虚拟主机销售系统,但是它被设计得高度插件化,除了虚拟主机也支持类似 ShadowSocks 或者 VPS 等服务的销售。

如果说得高端大气一点,RootPanel 是一个 SaaS 或者 PaaS 的开发框架,你可以在这个框架的基础上,以插件的形式销售自定义的服务。

RootPanel 目前支持哪些功能

RootPanel 目前支持两个典型的场景:Linux 虚拟主机、ShadowSocks 代理。

Linux 虚拟主机这个部分之前实现了支持 MongoDB, MySQL, Memcached, Redis 等数据库;通过 Nginx 共享 80 端口;通过 PHP-FPM 支持 PHP 网站,通过 Supervisor 支持 Node.js 和 Python, 以及 Golang 的应用。

但现在正在重写所有的插件,目前能用的只有 SSH 和 Supervisor.

除了具体的服务,RootPanel 提供了订单管理和工单系统的功能,形成一套完整的销售系统。

然后这里有一些截图:http://blog.rpvhost.net/?p=148

RootPanel 目前的进度如何

RootPanel 半年在来反复地重构,探索「正确」的写法。目前刚刚完成一次大规模的重构,正在重写之前的插件。接下来打算将插件提供的服务进一步抽象成「元件(原谅我想不出更恰当的词了)」,提供对多台服务器的支持,然后让服务可以在不同用户间交叉授权或转移。

我自己经营的虚拟主机服务 (http://rpvhost.net) 和代理服务 (http://greenshadow.net) 就在使用它。所以也不能完全算是纸上谈兵,但毕竟是一个刚刚完成的作品,用户很少,用起来不可避免地会出一些问题,存在一些风险。

总而言之这是一个还在成长阶段的项目,但毕竟花了这么多时间,实在想提前介绍给大家。

RootPanel 使用哪些技术

前端:BootStrap, jQuery, Coffee, 我不会写前端,所以代码惨不忍睹。

页面用 Jade, 由后端渲染;样式用 Less, 不过因为用了 Bootstrap, 只有很少的一点。

后端 Coffee, MongoDB, Redis, Express, Mongoose, 首要支持 Ubuntu 14.04.

RootPanel 是开源软件么

为了保留一些盈利的可能,RootPanel 使用 AGPL 和商业版本双授权。开源版本使用 AGPL, 这是一个比较丧心病狂的协议,要求用户在公开运行的服务器上使用并修改了 RootPanel 时,开源对其的修改。

SICP 笔记:1.2 – 1.2.6

计算机程序的构造和解释 是这样的一本书:它将大量编程中的基本技巧,用一本正经的语言讲出来,帮助你在构建大型软件时使用这些技巧来控制代码中的复杂性。而这些技巧的共性之一就是,将复杂的事物,通过隐藏细节的方式进行组合和抽象。我认为如果一个合格的程序员只需要读三本书的话,这本书便位列其中。

写笔记是为了自己在读书的过程中能够更好地思考和理解,这份笔记的定位是排除书中的例子,将书中的结论和观点用自己的话重新描述一遍,以简体中文版为主,因为似乎翻译的质量不高,所以也会参考英文原版。本书的英文原版以 CC-BY-SA 发行,相信公开发布这份笔记没有版权方面的问题。其实这份笔记对没有读过本书的人没有什么意义,因为本书的重要价值在于书中的例子,而这份笔记不会涉及这些例子,所以请纯当我在自言自语。

1.2 过程与它们产生的计算

在程序设计中,能够对计算过程中各种动作的进行情况进行规划是十分重要的,只有看清楚各种不同种类的过程会产生怎样的计算过程,才能更好地反向构造出可靠的程序,使之能表现出所需的行为。

1.2.1 线性的递归和迭代

计算阶乘可能有多种方式,其中一种基于这样的认识「对于一个正整数 n, 它的阶乘就等于 n * (n - 1)!」。另一种方法则可以将阶乘描述为「维持一个用来表示乘积的 product, 然后从 1 开始递增计数器 counter, 并将 product 设置为 product * counter, 直到 counter 大于 n, product 就是 n 的阶乘」。

从结果来看,这两种计算方法并没有什么差异,但如果我们考虑这两种方式的「形状」,即计算过程中时间和空间等资源的消耗情况,就会发现他们有很大区别。

第一个计算过程,如果使用前文提到的代换模型进行展开,会呈现出一种先逐步展开,而后收缩的形状。在展开阶段,这一计算过程构造器了一个「推迟执行的操作」所形成的链条,具体就是乘法运算,收缩阶段表现为这些运算的实际执行。这种类型的计算过程被称为「递归计算过程」,在执行时,解释器必须维护好那些以后要执行的操作的轨迹,在计算 n! 时,推迟执行的乘法链条的长度随 n 呈线性增长,这样的计算过程被称为「线性递归过程」。

与之对应的,第二个计算过程里并没有任何增长或收缩。对于任何一个 n, 在计算过程中的每一步,解释器需要保存的状态只有变量 product, countern 的当前值。我们称这种过程为「迭代计算过程」。一般来说,迭代计算过程就是那种其状态可以用固定数目的「状态变量」描述的计算过程;而与此同时,又存在着一套固定的规则,描述了计算过程在从一个状态转换到下一个状态时,这些变量的更新方式;以及一个通常都会有的结束检测,它描述了这一计算过程应该终止的条件。在计算 n! 时,所需的计算步骤随 n 线性增长,这种过程被称为「线性迭代过程」。

我们还可以换一个角度来对比这两个过程。在迭代的例子中,在计算过程中的任何一点,有关计算状态的完整描述都被包含在了那三个状态变量中。如果我们令上述计算过程在某两个步骤中停下来,在重新恢复这个计算过程时,只需要为编译器提供这三个变量的值。

而对于递归的计算过程而言,存在着一些「隐藏」的信息,它们并未保存在变量中,而是由解释器维持着,以指明在所推迟的运算所形成的链条中,当前计算过程所处的状态。这个链条越长,需要保存的信息就越多。

在后文中我们将会讨论过程在计算机上的实现,那时将会看到,所有的迭代过程都可以「以硬件的方式」实现为一个机器,其中只需要固定数目的寄存器,无需任何辅助存储器。而要实现递归计算过程,就需要一种机器,其中使用到了一种被称为「堆栈」的辅助数据存储结构。

在对比「迭代过程」和「递归过程」时,我们必须当心,不要混淆了「递归计算过程」和「递归过程」的概念。当我们说一个过程是递归的时候,描述的是一个代码的语法形式上的事实,说明这个过程的定义中直接或简介地引用了该过程本身。

而当我们说某一计算过程具有某种例如线性递归的模式时,我们说的是这一计算过程所产生的计算方式,而不是代码书写上的形式。当我们说某个递归过程将产生一个迭代的计算过程时,可能会让人感到很奇怪;但这一过程可能确实是迭代的,因为它的状态可以通过固定数目的状态变量来完全刻画,解释器在执行这一计算过程时,只需要保持这三个变量的状态就够了。

区分计算过程和写出的代码可能令人看到困惑,其中的一个原因在于一部分常见的编程语言的实现中,在执行递归的过程时,所需要消耗的内存总是与过程调用的深度成正比,即使这个过程所描述的计算过程从原理上看上去是迭代的。作为这一设计的后果,要在这些语言中描述迭代过程,必须借助于特殊的「循环结构」,例如 forwhile 等。

而在 Scheme 的实现中则没有这一缺陷,它总是能在常亮的内存消耗中执行迭代型的计算过程,即使这一计算过程是用一个递归过程描述的。具有这一特征的编程语言实现被称为「尾递归」的,有了支持尾递归的实现,我们就可以利用常规的过程调用机制来表述迭代,而不必借助专用的迭代结构。

1.2.2 树状递归

线性递归之外的另一种计算模式是「树状递归」,作为例子来考虑斐波那契数量的计算,除了前两位是 0 和 1 之外,其他的每个数都是前两个数之和。在 Lisp 中,我们可以简单地将 n 大于 1 的 fib(n) 表示为 fib(n - 1) + fib(n - 2).

当我们计算 fib(5) 的时候,我们需要计算 fib(4)fib(3), 而为了计算 fib(4) 又需要计算 fib(2), 这一展开过程将会是树状的,在每一层会形成两个分支,直到其中一个是 fib(1)fib(0).

这是一个典型的树状递归,但确实一种糟糕的计算斐波那契数列的方式,因为它进行了大量的冗余计算。该过程的计算步骤将会随着 n 呈指数增长,而空间需求则正比于 n, 因为在计算中的任何一点,只需要保存树中位于该节点之上的节点的轨迹。一般来说,树状递归中的计算步骤正比于树中的节点数,空间需求正比于树的最大深度。

如果用迭代方式重新实现 fib, 将会减少大量步骤,并减少一些空间需求,但并不是说树状递归是没有价值的。当在层次性的数据结构上进行操作时,树状递归是一种非常自然而威力强大的工具。而且它更加简单直接,如果将 fib 规划为迭代过程,则必须意识到,这一计算过程是通过三个状态变量来刻画的。

对待冗余计算的一种途径是通过重新安排,使计算过程能够自动构造出一个已经计算出的值的缓存,每次要求对某一参数调用过程时,先检查这个值是否在缓存中,如果存在就可以避免重复计算。

1.2.3 增长的阶

不同的计算过程在资源的消耗上存在着巨大的差别,衡量这种差别的一种方法就是用「增长阶」的记法。令 n 是有关问题规模的一个度量,例如如果是求一个数的平方根,n 就可以是所需的精度的位数。

R(n) 就是当问题规模为 n 时所需的资源的量。可能是所用到的寄存器的个数,也可能是需要执行的机器指令的个数,在每一时刻执行执行固定数目的操作的计算机里,所需的时间正比于需要执行的机器执行的条数。

如果存在常数 k1k2, 使得 R(n) 总是在 k1 * f(n)k2 * f(n) 之间,那我们就称 R(n) 具有 O(f(n)) 的增长阶。

举例来说,前文中计算阶乘的线性递归过程中,计算步骤的数目正比于 n, 也就是说具有 O(n) 的增长阶,其空间需求的增长阶也是 O(n). 而迭代版本的阶乘,步数具有 O(n) 的增长阶,而空间是 O(1), 即为一个常数。

增长阶是对计算过程的行为的一个粗略描述,如果一个三个计算过程分别需要 n^2, 1000 * n^2, 3 * n^2 * 10 * 10 + 17, 它们的增长阶都是 O(n^2).

增长阶为我们在问题规模改变时,预计一个计算过程的行为变化提供了有用的线索。对于一个增长阶为 O(n) 的计算过程,规模增大一倍将使它所用的资源也增加一倍。对于一个 O(n^2) 的计算过程,问题规模每增加一,都将导致所用资源按倍增长。而对于 O(log n) 的计算过程,问题规模每增加一倍,所需资源只增加一个常数。

1.2.4 求幂

考虑对一个给定的数计算幂乘,一个简单的做法是将其定义为一个递归过程 expt(x, n): x * expt(x, n - 1) 直到 n 为 0 时 expt(0) 为 1. 这个线性递归计算需要 O(n) 的计算步骤和 O(n) 的空间。

如果像阶乘一样将其转换为一个等价的迭代算法,则需要 O(n) 的计算步骤和 O(1) 的空间。

因为 x^a * x^b 等于 x ^ (a + b) 所以我们可以利用这个规则将 expt(x, n) 定义成当 n 是偶数时为 expt(x, (n / 2)) ^ 2, 奇数时为 x * expt(x, n - 1). 这个算法在时间上和空间上都有 O(log n) 的增长阶,在计算 x ^ 2n 时只比 x ^ n 多一次乘法。

随着 n 的变大,O(log n)O(n^2) 之间的差距会越来越大。

1.2.5 最大公约数

1.2.6 实例:质数检测

费马检查与我们前面熟悉的算法都不一样,前面的算法都保证了结果一定正确,而费马检查得到的结果只有概率上的正确性。如果一个数不能够通过费马检查,我们可以确定它一定不是质数,但如果一个数通过了费马检查,我们只能认为它有很大的可能性是质数。如果执行这个检查的次数足够多,就可以将这一检查出错的概率减少到可以接受的程度。这类算法被称为「概率算法」。

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2025-02-10.