warp静态文件服务器评测 2011-11-12
安装
安装GHC和Cabal,参考: http://book.realworldhaskell.org/read/installing-ghc-and-haskell-libraries.html
使用Cabal安装 Warp 服务器和 wai-app-static ,Cabal将自动下载安装依赖的一些库:
cabal install warp wai-app-static
编写我们的静态文件服务器:
import Control.Applicative ( (<$>) )
import Data.Maybe (fromMaybe, listToMaybe)
import System.Environment (getArgs)
import Network.Wai.Handler.Warp (run)
import Network.Wai.Application.Static ( staticApp
, defaultFileServerSettings)
main :: IO ()
main = do
port <- read . fromMaybe "3000" . listToMaybe <$> getArgs
run port $ staticApp defaultFileServerSettings
特性
一个完善的静态文件服务器:
- 能以高阶函数的方式定制文件查找逻辑
- 根据文件扩展名产生mimetype
- 304响应,支持If-Modified-Since,支持If-None-Match匹配文件哈希值
- 目录末尾自动添加'/' ,自动查找可配置的index文件等等。
性能
- 环境:Thinkpad X61, T8100 双核,Linux 2.6.38,GHC 7.2.1
- 编译选项: ghc -O3 -threaded Main.hs
- 执行选项: ./Main +RTS -N1 -qa
- 对照nginx:worker 1, sendfile on
因为我这个破本只有两个核,一个用来运行 ab ,一个可以用来运行web server,所以上面都只配置一个worker进程。
测试命令: ab -c 100 -n 100000 -r http://localhost:3000/test.html
测试6次,平均每秒请求数分别为:
1 7174.23
2 6946.33
3 6120.31
4 6819.33
5 7373.51
6 6776.65
对比nginx:
1 13543
2 13601.69
3 13512.38
4 13654.39
5 13680.97
6 13630.64
warp pong test
顺便再测下warp,把静态文件app去掉,换上一个最简单的app:
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative ( (<$>) )
import Data.Maybe (fromMaybe, listToMaybe)
import System.Environment (getArgs)
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp (run)
import Blaze.ByteString.Builder (fromByteString)
pong :: Application
pong req = return $ ResponseBuilder
statusOK
[("Content-Type", "text/plain")]
(fromByteString "pong")
main :: IO ()
main = do
port <- read . fromMaybe "3000" . listToMaybe <$> getArgs
run port pong
相同条件下测试,平均每秒请求数为:
1 22184.93
2 22232.89
3 22150.83
4 22189.02
5 22267.33
6 22125.08
nginx的话,好像没有办法构造一个等价测试案例,我配置了一个最简单的server block:
server {
server_name localhost;
location = / {
}
}
然后测试 http://localhost/ 这个 404 的响应,结果跟上面nginx返回静态文件的结果类似。
结论
warp pong 测试结果很惊人,看来 static app 还有不小的优化空间的。nginx主要是用来做个对照,没看过nginx代码,不一定公平。期待大家在不同环境下去测试测试,看结果如何。
无题 2011-11-03
写 parser 的时候需要写这样的代码:
其中 pred 是个判别函数,签名为: Char -> Bool 。 takeTill 的作用就是一直解析到 pred 返回 True 为止。
比如你要解析到下一个 > 或者 = ,你就写:
想解析到下一个空白字符为止,就写:
想解析各种空白字符呢,就在上面的基础上取个反:
这个例子是想提醒大家 isSpace 是个函数,所以这里需要进行函数组合,而不是直接调用 not 。
那我今天想说的是什么呢。现在我想解析到下一个 > 或 = 或空白字符为止,也就是说需要把前两个拼起来,直接写起来是这样的:
takeTill (\c -> inClass ">=" c || isSpace c)
也不麻烦,只不过对于患有 代码洁癖 的我来说,视觉上还不太给力。于是,我继续重构如下:
takeTill (inClass ">=" ||. isSpace)
多实现一个组合函数 ||. :
(||.) :: (a -> Bool) -> (a -> Bool) -> a -> Bool
(||.) f g a = f a || g a
把两个判别函数组合成一个新的判别函数。
为了将它推广到其他的组合操作,我们进一步泛化一个通用函数,姑且取名叫 fn 吧 :
fn :: (b -> b -> b) -> (a -> b) -> (a -> b) -> a -> b
fn op f g a = f a `op` g a
前面的 ||. 就成为:
大功告成,正当我准备好好欣赏一下最终的产物 fn 的时候,却突然发现, fn 的作用不就是把 b 层面的二元函数提升到 (a -> b) 层面么,正如 || 和 ||. 都是或操作,只不过一个作用在 Bool 值层面,一个作用在 a -> Bool 判别函数层面。如此通用的概念,我意识到我很可能重造轮子了。
于是我请 lambdabot mm帮我诊断一下:
<huangyi> @pl fn op g k a = g a `op` k a
<lambdabot> fn = liftM2
果然,小mm告诉我, fn 其实就是 liftM2 。 liftM2 是专门用来把二元函数提升到 Monad 中去的,而 ((->) a) 正是 Monad 的实例。
instance Monad ((->) a) where
return = const
-- (>>=) :: (a -> b) -> (b -> a -> c) -> (a -> c)
f >>= g = g . f
liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 op ma mb = do
a <- ma
b <- mb
return (a `op` b)
其实只需要提升一下抽象层次的话,还不需动用 Monad 这样的大杀器, Applicative 也可以搞定。
instance Applicative ((->) a) where
-- <$> :: (b -> c) -> (a -> b) -> (a -> c)
f <$> g = f . g
-- (<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c)
f <*> g = \a -> (f a) (g a)
用 Applicative 的话,前面的 fn 就等价于 liftA2 了。
liftA2 :: Application f => (a -> b -> c) -> f a -> f b -> f c
liftA2 op fa fb = op <$> fa <*> fb
绕了一圈,最后还是没逃出Haskell最基本的框框。
Haskell Type Class 介绍 2011-10-15
写程序没啥灵感了,不如写点博客吧。
typeclass 作为 haskell 一大标志性特性,还是很值得介绍介绍的。
珠三角技术沙龙介绍Haskell Web开发 2011-09-24
2011年09年17日,再次跑到珠三角技术沙龙忽悠Haskell,这次是广州,侧重Haskell服务器端以及Web开发中的实用功能。 沙龙活动总结
类型推导和合一算法的简单介绍 2011-06-04
Hindley Milner类型系统最过瘾的就是 类型推导 功能。写程序的时候可以完全忽略变量和函数的类型,由编译器自动推导类型并做类型检查。可以说是静态语言和动态语言的完美结合。本文简单介绍类型推导的过程,以及其中的关键算法: 合一 。
先问一个问题, flip id 的类型是什么?我们知道 id 是原封不动地返回传给它的参数,类型是 a -> a ; flip 是用来交换一个函数前两个参数的位置,类型是 (a -> b -> c) -> b -> a -> c ,然而用 id 去调用 flip 返回的是个什么类型?心算起来还是有难度,幸好我们可以求助于 ghci :
Prelude> :t id
id :: a -> a
Prelude> :t flip
flip :: (a -> b -> c) -> b -> a -> c
Prelude> :t flip id
flip id :: b -> (b -> c) -> c
Haskell是如何算出这个结果的?这就是类型推导算法在起作用了。简单地说,类型推导过程分两部进行:1. 根据类型系统规则产生一个方程组;2.解方程组。
比如上面这个问题,我们先假定 flip id 的类型是 x ,然后根据函数调用的规则以及 id 的类型,我们发现 flip 的类型应该是 (a -> a) -> x ,同时我们已知 flip 的类型是: (a -> b -> c) -> b -> a -> c ,这两个写法必须是等价的。根据这一点,我们就能得出一个方程组,通过合一算法解方程组,我们就可以得到 x 的值。
首先为了防止命名冲突,先进行必要的重命名,同时对 flip 的类型进行一点等价转换,现在两个类型便成为如下形式:
(d -> d ) -> x
(a -> (b -> c)) -> (b -> a -> c)
根据其中的对应关系,我们可以得出这些一样等式:
d = a
d = b -> c
x = b -> a -> c
然后再通过一些等价替换就不难解出 x 的值了: b -> (b -> c) -> c 。
我用Haskell写了一个 合一算法的简单实现 ,供大家学习参考,运行效果如下:
*Main> test (f [d, d, x]) (f [a, f [b, c], f [b, a, c]])
f(d,d,x) <==> f(a,f(b,c),f(b,a,c))
d -> f(b,c)
x -> f(b,f(b,c),c)
a -> f(b,c)