常用编程语言和库的正则表达式性能对比

📅 2026/7/10 7:02:58 👁️ 阅读次数 📝 编程学习
常用编程语言和库的正则表达式性能对比

Golang 测试代码
首先是 Go 的代码。regexp2go 是个命令行工具,它会把给定的正则翻译成等价的 Go 语言实现,我们要先生成代码:

$ go install github.com/CAFxX/regexp2go@latest
$ regexp2go -pkg re -fn MatchInfoLine ‘“TSLA.*?”’ > ‘re/info_line.go’
生成的代码会在 re 这个包里。接着我们读入测试文件,并比较标准库和生成代码:

package main

import (
“bufio”
“fmt”
“os”
“regexp”
“rtest/re”
“time”
)

func matchWithRegexp(lines []string, re *regexp.Regexp) int64 {
var matches int64 = 0
for _, line := range lines {
if re.MatchString(line) {
matches++
}
}
return matches
}

func matchWithRegexp2go(lines []string) int64 {
var matches int64 = 0
matcher := re.MatchInfoLine{}
for _, line := range lines {
_, _, ok := matcher.FindString(line)
if ok {
matches++
}
}
return matches
}

func main() {
filename := “test.data”
file, err := os.Open(filename)
if err != nil {
fmt.Printf(“无法打开文件: %v\n”, err)
return
}
defer file.Close()

var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) } if err := scanner.Err(); err != nil { fmt.Printf("读取文件出错: %v\n", err) return } fmt.Printf("文件读取完成。总行数: %d\n\n", len(lines)) if len(lines) == 0 { return } pattern := `\"TSLA.*?\"` re, err := regexp.Compile(pattern) if err != nil { fmt.Printf("正则编译失败: %v\n", err) return } const iterations = 2 { var totalMatches int64 start := time.Now() for range iterations { totalMatches += matchWithRegexp(lines, re) } totalDuration := time.Since(start) avgDurationNs := totalDuration.Nanoseconds() / int64(iterations) avgDurationMs := float64(avgDurationNs) / 1e6 fmt.Printf("[Go regexp 结果] -------\n") fmt.Printf("循环总次数: %d\n", iterations) fmt.Printf("总耗时: %v\n", totalDuration) fmt.Printf("单次扫描平均耗时: %.4f ms\n", avgDurationMs) fmt.Printf("累计匹配成功行数: %d\n", totalMatches) } { var totalMatches int64 start := time.Now() for range iterations { totalMatches += matchWithRegexp2go(lines) } totalDuration := time.Since(start) avgDurationNs := totalDuration.Nanoseconds() / int64(iterations) avgDurationMs := float64(avgDurationNs) / 1e6 fmt.Printf("\n[Go regexp2go 结果] -------\n") fmt.Printf("循环总次数: %d\n", iterations) fmt.Printf("总耗时: %v\n", totalDuration) fmt.Printf("单次扫描平均耗时: %.4f ms\n", avgDurationMs) fmt.Printf("累计匹配成功行数: %d\n", totalMatches) }

}
我选择测试的正则是 “TSLA.*?”,这种正则在业务中很常用。测试时把文件内容全部读入内存,以免结果受到 I/O 的干扰。

C++ 测试代码
C++ 的测试也一样:先读入文件内容,然后分别测试标准库和 PCRE2:

#include
#include
#include
#include
#include
#include

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

// 1. std::regex 匹配函数
long long match_with_std_regex(const std::vectorstd::string& lines, const std::regex& txt_regex) {
long long matches = 0;
for (const auto& line : lines) {
if (std::regex_search(line, txt_regex)) {
matches++;
}
}
return matches;
}

// 2. PCRE2 匹配函数
long long match_with_pcre2(const std::vectorstd::string& lines, pcre2_code* re, pcre2_match_data* match_data) {
long long matches = 0;
for (const auto& line : lines) {
int rc = pcre2_match(
re,
(PCRE2_SPTR)line.c_str(),
line.length(),
0, 0,
match_data,
NULL
);
if (rc >= 0) {
matches++;
}
}
return matches;
}

// 3. PCRE2 JIT
long long match_with_pcre2_jit(const std::vectorstd::string& lines, pcre2_code* re, pcre2_match_data* match_data) {
long long matches = 0;
for (const auto& line : lines) {
int rc = pcre2_jit_match(
re,
(PCRE2_SPTR)line.c_str(),
line.length(),
0, 0,
match_data,
NULL
);
if (rc >= 0) {
matches++;
}
}
return matches;
}

int main() {
std::string filename = “test.data”;
std::ifstream infile(filename);
if (!infile.is_open()) {
std::cerr << "无法打开文件: " << filename << std::endl;
return 1;
}

std::vector<std::string> lines; std::string line; while (std::getline(infile, line)) { lines.push_back(line); } infile.close(); std::cout << "文件读取完成。总行数: " << lines.size() << "\n\n"; if (lines.empty()) return 0; const int ITERATIONS = 2; // ------------------ 1. std::regex 测试 ------------------ { std::regex txt_regex(R"(\"TSLA.*?\")"); long long total_matches = 0; auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < ITERATIONS; ++i) { total_matches += match_with_std_regex(lines, txt_regex); } auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double, std::milli> total_duration = end - start; std::cout << "[std::regex 结果] -------\n" << "循环总次数: " << ITERATIONS << "\n" << "总耗时: " << total_duration.count() << " ms\n" << "单次扫描平均耗时: " << (total_duration.count() / ITERATIONS) << " ms\n" << "累计匹配成功行数: " << total_matches << "\n\n"; } // ------------------ 2. PCRE2 测试 ------------------ { int errorcode; PCRE2_SIZE erroroffset; PCRE2_SPTR pattern = (PCRE2_SPTR)"\\\"TSLA.*?\\\""; pcre2_code* re = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, 0, &errorcode, &erroroffset, NULL); if (re == NULL) { std::cerr << "PCRE2 编译失败\n"; return 1; } pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, NULL); long long total_matches = 0; auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < ITERATIONS; ++i) { total_matches += match_with_pcre2(lines, re, match_data); } auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double, std::milli> total_duration = end - start; pcre2_match_data_free(match_data); pcre2_code_free(re); std::cout << "[PCRE2 结果] ------------\n" << "循环总次数: " << ITERATIONS << "\n" << "总耗时: " << total_duration.count() << " ms\n" << "单次扫描平均耗时: " << (total_duration.count() / ITERATIONS) << " ms\n" << "累计匹配成功行数: " << total_matches << "\n\n"; } // ------------------ 3. PCRE2 JIT 测试 ------------------ { int errorcode; PCRE2_SIZE erroroffset; PCRE2_SPTR pattern = (PCRE2_SPTR)"\\\"TSLA.*?\\\""; pcre2_code* re = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, 0, &errorcode, &erroroffset, NULL); if (re == NULL) { std::cerr << "PCRE2 编译失败\n"; return 1; } int jit_rc = pcre2_jit_compile(re, PCRE2_JIT_COMPLETE); if (jit_rc < 0) { // 返回负数说明当前平台或环境不支持 JIT(例如某些高安全性系统禁用了内存执行权限) // 此时它会退回到普通的非 JIT 模式,代码依然能跑,但速度会慢 std::cout << "当前环境不支持 JIT 编译,退回到普通模式\n"; } else { std::cout << "PCRE2 JIT 编译成功启用!\n"; } pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, NULL); long long total_matches = 0; auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < ITERATIONS; ++i) { total_matches += match_with_pcre2(lines, re, match_data); } auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double, std::milli> total_duration = end - start; pcre2_match_data_free(match_data); pcre2_code_free(re); std::cout << "[PCRE2 JIT 结果] ------------\n" << "循环总次数: " << ITERATIONS << "\n" << "总耗时: " << total_duration.count() << " ms\n" << "单次扫描平均耗时: " << (total_duration.count() / ITERATIONS) << " ms\n" << "累计匹配成功行数: " << total_matches << "\n\n"; } return 0;

}
C++ 还顺带测试了 PCRE2 的 JIT 编译器,它支持几乎所有常见 CPU 平台,甚至包括龙芯,并且可以极大提高匹配性能。PCRE2 里我还开启了 Unicode 字符处理,在这个场景里这是必要的;开启这个处理会造成一点轻微的性能下降。

编译命令是 g++ -std=c++23 -Wall -O3 -lpcre2-8 a.cpp

Python 测试代码
Python 虽然本身运行缓慢,但它的标准库并不慢,因此我选择它作为性能对比的基线:

import re
import time
import sys

def match_with_re(lines, compiled_re):
matches = 0
for line in lines:
if compiled_re.search(line):
matches += 1
return matches

def main():
filename = “test.data”

try: with open(filename, "r", encoding="utf-8") as f: lines = f.read().splitlines() except FileNotFoundError: print(f"无法打开文件: {filename}", file=sys.stderr) return print(f"文件读取完成。总行数: {len(lines)}\n") if not lines: return pattern = r'\"TSLA.*?\"' compiled_re = re.compile(pattern) iterations = 2 total_matches = 0 start_time = time.perf_counter() for _ in range(iterations): total_matches += match_with_re(lines, compiled_re) end_time = time.perf_counter() total_duration_secs = end_time - start_time total_duration_ms = total_duration_secs * 1000 avg_duration_ms = total_duration_ms / iterations print(f"[Python re 结果] -------") print(f"循环总次数: {iterations}") print(f"总耗时: {total_duration_ms:.2f} ms") print(f"单次扫描平均耗时: {avg_duration_ms:.4f} ms") print(f"累计匹配成功行数: {total_matches}")

ifname== “main”:
main()
测试结果
尽管只运行了两轮,但每个函数匹配的次数都在 1000 万次以上,足够摊平统计差异了。

下面是测试结果:

实现 总耗时 单次扫描平均耗时
Go regexp 1.771614875 s 885.8074 ms
Go regexp2go 1m27.618973125 s 43809.4866 ms
Python re 2038.25 ms 1019.1254 ms
std::regex 20775.7 ms 10387.8 ms
PCRE2 4625.82 ms 2312.91 ms
PCRE2 JIT 238.041 ms 119.02 ms
实现 总耗时倍率 单次扫描平均耗时倍率
Go regexp 0.87× 0.87×
Go regexp2go 43.02× 42.99×
Python re 1.00× 1.00×
std::regex 10.19× 10.19×
PCRE2 2.27× 2.27×
PCRE2 JIT 0.12× 0.12×
从结果来看,regexp2go 是最慢的。这个工具号称比 Go 1.16 的标准库最多快 5 倍,要么是 Go 的标准库有了飞跃式提升,要么是它夸大宣传。

C++ 标准库的 regex 慢是众所周知的,不过没想到会比 Python 基线慢一个数量级,令人捧腹。不同的标准库实现之间性能也是天差地别,我选用了最快的 libstdc++;如果换成 LLVM 的 libc++,性能会回退到和 regexp2go 一桌。除了慢之外,标准库还有代码膨胀的问题,仅仅简单使用基础功能和一个不算复杂的模式,就产生了 200 KB 左右的编译产物。

令人意外的是,PCRE2 在未开启 JIT 时居然会比 Python 慢。这是因为对于非贪婪匹配,PCRE2 的引擎会比 Go 和 Python 做更多工作,最终导致速度变慢。