☰
FrameworkBenchmarks 中的 IHP 基准测试实现:基于 Haskell 全栈框架的六项性能测试解析
2026/10/15 3:24:45 网站建设 项目流程
  • 性能测试
  • 测试
  • 后端
  • 开发者工具

【免费下载链接】FrameworkBenchmarks

Source for the TechEmpower Framework Benchmarks project

项目地址:https://gitcode.com/gh_mirrors/fr/FrameworkBenchmarks
点击查看免费下载

本篇技术指南以 frameworks/Haskell/ihp/README.md 为纲,结合仓库内 IHP 应用的源码、配置与构建脚本,系统讲解 TechEmpower FrameworkBenchmarks 中 IHP 基准测试的实现方式:六个测试端点的路由、控制器逻辑、数据库模型、视图渲染与性能调优参数,以及本地验证这些端点的方法。读完本文,你将能独立定位 IHP 基准实现的每一处代码,理解queries参数边界规则与数据库连接池等关键配置的底层作用。

一、IHP 基准测试概览

IHP(Integrated Haskell Platform)是一个面向生产环境的 Haskell 全栈 Web 框架。在 FrameworkBenchmarks 仓库中,它的基准实现位于 frameworks/Haskell/ihp,其定位由 config.toml 与 benchmark_config.json 共同声明:

  • Approach:Realistic(贴近真实生产场景)
  • Classification:Fullstack(全栈框架)
  • Database:postgres
  • ORM:Full(使用完整的 ORM 能力)
  • Platform / Webserver:Wai(基于 WAI 接口与 Warp 服务器)
  • Language:Haskell

config.toml中同时登记了六个测试端点的 URL 映射,这是基准测试工具定位路由的依据:

[main] urls.plaintext = "/plaintext" urls.json = "/json" urls.db = "/db" urls.query = "/query?queries=" urls.update = "/update?queries=" urls.fortune = "/fortunes"

二、六项测试的实现源码位置

根据 frameworks/Haskell/ihp/README.md 的指引,所有测试逻辑集中在一个控制器文件 src/Web/Controller/FrameworkBenchmarks.hs 中,各测试类型的实现位置如下:

测试类型源码位置(行号)对应 Action
JSONFrameworkBenchmarks.hs#L15JsonAction
DBFrameworkBenchmarks.hs#L18DbAction
QUERYFrameworkBenchmarks.hs#L23QueryAction
FORTUNESFrameworkBenchmarks.hs#L32FortuneAction
UPDATEFrameworkBenchmarks.hs#L39UpdatesAction
PLAINTEXTFrameworkBenchmarks.hs#L52PlaintextAction

控制器数据类型FrameworkBenchmarksController定义在 src/Web/Types.hs,六个构造子与上表一一对应:

data FrameworkBenchmarksController = JsonAction | PlaintextAction | DbAction | QueryAction | FortuneAction | UpdatesAction deriving (Eq, Show, Data)

三、测试 URL 与路由映射

README 列出了六项测试的完整访问地址(基准容器监听 8080 端口):

测试URL
JSONhttp://localhost:8080/json
PLAINTEXThttp://localhost:8080/plaintext
DBhttp://localhost:8080/db
QUERYhttp://localhost:8080/query?queries=
UPDATEhttp://localhost:8080/update?queries=
FORTUNEShttp://localhost:8080/fortunes

这些路径的路由规则定义在 src/Web/Routes.hs,通过 IHP 的parseRoute'组合子完成字符串匹配与 action 的绑定:

instance CanRoute FrameworkBenchmarksController where parseRoute' = (string "/json" <* endOfInput >> pure JsonAction) <|> (string "/plaintext" <* endOfInput >> pure PlaintextAction) <|> (string "/db" <* endOfInput >> pure DbAction) <|> (string "/fortunes" <* endOfInput >> pure FortuneAction) <|> (string "/query" <* endOfInput >> pure QueryAction) <|> (string "/update" <* endOfInput >> pure UpdatesAction)

注意endOfInput的存在意味着路径必须精确匹配,/json/这类带尾斜杠的请求不会被匹配。路由解析后由 src/Web/FrontController.hs 挂载到应用前端控制器,最终由 src/Main.hs 中的main = IHP.Server.run config启动服务。

四、数据库模型:World 与 Fortune

所有涉及数据库的测试(DB、QUERY、UPDATE、FORTUNES)都基于两张表,其定义见 src/Application/Schema.sql:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TABLE World ( id SERIAL PRIMARY KEY NOT NULL, randomnumber INT DEFAULT 0 NOT NULL ); CREATE TABLE Fortune ( id SERIAL PRIMARY KEY NOT NULL, message TEXT NOT NULL );
  • World表对应id与randomnumber两个字段,用于 DB / QUERY / UPDATE 测试;
  • Fortune表对应id与message,用于 FORTUNES 测试。

在控制器中,这两个模型直接由 IHP 的 ORM 生成类型Generated.Types提供,并通过自定义ToJSON实例控制 JSON 输出的字段名。注意randomnumber字段在 JSON 中被映射为驼峰式randomNumber(见 FrameworkBenchmarks.hs#L8-L9):

instance ToJSON World where toJSON world = object [("id" .= get #id world), ("randomNumber" .= get #randomnumber world)]

五、六个测试端点的实现详解

5.1 JSON:返回固定 JSON 消息

实现于 FrameworkBenchmarks.hs#L15-L16。这是最轻量的测试,不访问数据库,直接通过renderJson输出:

action JsonAction = do renderJson (toJSON (object ["message" .= ("Hello, World!" :: Text)]))

5.2 PLAINTEXT:返回纯文本

实现于 FrameworkBenchmarks.hs#L58-L59,同样不访问数据库:

action PlaintextAction = do renderPlain "Hello, World!"

5.3 DB:单条世界记录查询

实现于 FrameworkBenchmarks.hs#L18-L21。逻辑为:生成1..10000范围内的随机 id,再按主键查询World记录并返回 JSON:

action DbAction = do randomNumber :: Int <- randomRIO (1, 10000) randomWorld <- query @World |> findBy #id (Id randomNumber) renderJson (toJSON randomWorld)

这里使用了 IHP 的query @World |> findBy #id查询链,即按id字段精确查找的 ORM 操作。

5.4 QUERY:多次随机查询(支持 queries 参数)

实现于 FrameworkBenchmarks.hs#L23-L33。核心逻辑:

  1. 读取queries查询参数,通过defaultParam容错(参数缺失或非法时回退为1);
  2. 用toBoundaries将数量钳制到[1, 500]区间;
  3. 使用Async.mapConcurrently并发执行 N 次随机记录查询,最后一次性renderJson返回结果数组。
action QueryAction = do let queries = defaultParam (paramOrError @Int "queries") |> toBoundaries let fetchRandomWorld i = do randomWorldId :: Id World <- Id <$> randomRIO (1, 10000) fetch randomWorldId [1..queries] |> Async.mapConcurrently fetchRandomWorld >>= renderJson where defaultParam (Right a) = a defaultParam _ = 1

边界钳制函数toBoundaries定义在文件末尾(L61-L64),这是基准测试对queries参数的强制约束:

toBoundaries n | n < 1 = 1 | n > 500 = 500 | otherwise = n

5.5 UPDATE:读取并更新随机记录

实现于 FrameworkBenchmarks.hs#L42-L56。与 QUERY 结构对称:同样解析并钳制queries参数、同样用Async.mapConcurrently并发执行;不同之处在于每次操作包含「取随机 id → 取记录 → 生成新的随机数 → 更新randomnumber字段 → 写回数据库」五步:

action UpdatesAction = do let queries :: Int = (defaultParam $ paramOrError @Int "queries" ) |> toBoundaries let updateRandomWorld i = do randomWorldId :: Id World <- Id <$> randomRIO (1, 10000) newRandom :: Int <- randomRIO (1, 10000) world <- fetch randomWorldId world |> set #randomnumber newRandom |> updateRecord [1..queries] |> Async.mapConcurrently updateRandomWorld >>= renderJson where defaultParam (Right a) = a defaultParam _ = 1

其中set #randomnumber newRandom |> updateRecord是 IHP 的 ORM 更新链式写法:先修改字段,再调用updateRecord持久化。

5.6 FORTUNES:数据排序与 HSX 模板渲染

实现于 FrameworkBenchmarks.hs#L35-L40,是唯一需要完整页面渲染的测试:

  1. 取出全部Fortune记录;
  2. 新建一条消息为"Additional fortune added at request time."的记录;
  3. 按message字段对合并后的列表排序;
  4. 通过renderHtml交给 HSX 视图渲染并响应。
action FortuneAction = do allFortunes :: [Fortune] <- query @Fortune |> fetch let newFortune :: Fortune = newRecord @Fortune |> set #message "Additional fortune added at request time." let compareFortunes :: Fortune -> Fortune -> Ordering = \f1 f2 -> compare (get #message f1) (get #message f2) let fortunes :: [Fortune] = sortBy compareFortunes (newFortune:allFortunes) renderHtml FortuneView { .. } >>= respondHtml

HTML 模板定义在 src/Web/View/FrameworkBenchmarks/Fortune.hs,使用 IHP 的 HSX 内嵌 HTML 语法生成符合规范的表格页面,并通过preEscapedToHtml声明<!DOCTYPE html>:

instance View FortuneView where beforeRender _ = setLayout \v -> v html FortuneView { .. } = preEscapedToHtml ("<!DOCTYPE html>" :: Text) <> [hsx| <html> <head><title>Fortunes</title></head> <body> <table> <tr> <th>id</th> <th>message</th> </tr> {forEach fortunes renderFortunes} </table> </body> </html> |]

注意beforeRender中使用了setLayout \v -> v(恒等布局),即绕过 src/Web/View/Layout.hs 中默认的defaultLayout页面壳,直接输出最小化的测试页面,避免额外 HTML 开销。

六、运行配置与性能调优

6.1 应用级配置

src/Config/Config.hs 定义了应用启动参数,其中有两项直接影响基准性能:

config :: ConfigBuilder config = do option Production option (AppHostname "localhost") option (RequestLoggerMiddleware $ \application -> application) option (DBPoolIdleTime 10) option (DBPoolMaxConnections 512)
  • Production:强制生产模式(与开发模式相比会加载/prod.css、/prod.js等精简资源,见 Layout.hs);
  • DBPoolMaxConnections 512:将数据库连接池上限抬升到 512,支撑 QUERY / UPDATE 测试中的高并发查询(并发由Async.mapConcurrently发起);
  • DBPoolIdleTime 10:空闲连接回收时间(秒)。

6.2 Docker 构建与启动参数

ihp.dockerfile 采用 Nix 方式构建(基于nixos/nix镜像、使用cachix use digitallyinduced获取 IHP 缓存),并通过 flake 构建optimized-prod-server产物。关键运行时配置:

ENV DATABASE_URL=postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world ENV PORT=8080 EXPOSE 8080 CMD ./result/bin/RunProdServer +RTS -A32m -N$(nproc) -qn2 -M2G -RTS
  • DATABASE_URL:连接基准环境的 PostgreSQL(库名hello_world,账号benchmarkdbuser);
  • PORT=8080:服务监听端口,与 README 中的测试 URL 一致;
  • GHC RTS 参数:-A32m(扩大分配区减少 GC 次数)、-N$(nproc)(启用多核并行)、-qn2(限制并行 GC 线程)、-M2G(堆上限 2GB)。

6.3 Nix flake 依赖锁定

flake.nix 将 IHP 版本锁定为v1.3.1,并显式声明wai、text、cabal-install等构建依赖。nixpkgs-config.nix则通过make-nixpkgs-from-options.nix集成额外的 Haskell 包来源,保证构建可复现。

七、本地验证测试端点

按 README 提供的 URL,可以在构建并启动基准容器后逐一验证各测试端点:

# JSON 测试 curl http://localhost:8080/json # 期望输出: {"message":"Hello, World!"} # PLAINTEXT 测试 curl http://localhost:8080/plaintext # 期望输出: Hello, World! # DB 测试 curl http://localhost:8080/db # 期望输出: {"id":<随机id>,"randomNumber":<随机数>} # QUERY 测试(queries 指定查询条数) curl "http://localhost:8080/query?queries=10" # 期望输出: 10 条 World 记录的 JSON 数组 # UPDATE 测试(queries 指定更新条数) curl "http://localhost:8080/update?queries=10" # 期望输出: 10 条更新后 World 记录的 JSON 数组 # FORTUNES 测试 curl http://localhost:8080/fortunes # 期望输出: 按 message 排序的完整 HTML 表格(含追加的 fortune)

验证时可结合queries参数的边界规则:传queries=0或负数会被钳制为 1,传超过 500 的值会被钳制为 500(对应 FrameworkBenchmarks.hs#L61-L64 的toBoundaries),缺省时回退为 1。

八、实现要点总结

综合 README 与源码,可以归纳 IHP 基准实现的关键设计:

  1. 单一控制器承载全部测试:六个 action 集中在 FrameworkBenchmarks.hs,便于基准工具按行号定位实现;
  2. queries参数三段式处理:defaultParam容错回退 →paramOrError @Int解析 →toBoundaries钳制[1, 500],QUERY 与 UPDATE 共用这套规则;
  3. 并发查询依赖库级能力:借助Control.Concurrent.Async的mapConcurrently实现并行数据库访问,配合 512 连接上限的连接池(Config.hs)支撑高并发负载;
  4. FORTUNES 走完整渲染链路:查询 → 追加记录 →sortBy排序 → HSX 模板输出,且以恒等布局剥离多余页面壳,保持输出与基准规范一致;
  5. 构建与运行链路清晰:flake 锁定 IHP v1.3.1,Dockerfile 以 Nix 产物RunProdServer启动,数据库连接与端口通过环境变量注入。

以上内容全部可从 frameworks/Haskell/ihp 目录下的 README、源码与配置文件中直接验证,读者可按需深入各文件继续研究。

  • 性能测试
  • 测试
  • 后端
  • 开发者工具

【免费下载链接】FrameworkBenchmarks

Source for the TechEmpower Framework Benchmarks project

项目地址:https://gitcode.com/gh_mirrors/fr/FrameworkBenchmarks
点击查看免费下载

相关推荐

上一篇:3分钟解决Windows无法预览iPhone照片的终极方案
下一篇:网盘直链下载助手完整教程:告别限速困扰,一键获取真实下载链接

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询