有没有一种快速方法可以在 SQLite 和/或 Tcl 中将字符串分割成相等长度的段?
我的目标是在内存中获取一个字符串并将分割写入表中的行。有时字符串通过通道传递,有时它是由 SQLite 表中已保存的片段连接而成。
我一直在使用具有
substr
的 CTE,对于将约 1 MB 的字符串分解为 4216 条/行,我能得到的最好时间约为 1.6 秒。
最简单的拆分尝试示例是
select
sg.value, substr(:textContent, sg.value, 250)
from
generate_series(1, :tcLen, 250) sg
;
使用
group_concat
组合表中的 4216 个片段仅需 0.129 秒,其中每行上都有一个 substr
来选择每行字符串的全部或部分。
感谢您提供的任何指导。
编辑:
我现在看到这个answer在
:textutil::spit::splitn
中提到了tcllib
。会尝试一下;应该考虑到那里查看,但假设 SQL 会更快。
使用如下文本实用程序并在循环中插入,将 1.6 秒减少到约 0.26 秒。
foreach row [::textutil::split::splitn $textContent $bufferRowMaxLength] {
set charLength [expr {min([string length $row], $bufferRowMaxLength)}]
db eval {
insert into mem.pt_buffers values ( ... );
insert into mem.pt_pointer values ( ... );
}
}
textutil::split::splitn
非常快,尽管它只是该解决方案的明显 Tcl 脚本,比我用 c 所能做到的最佳速度慢大约 2 倍。这其实不应该太令人惊讶,Tcl 确实很擅长处理字符串。
我尝试过的一个替代方案(基于 Donal 多年前分享的一个技巧),是用
regexp
: 将其分块
set chunks [regexp -all -inline .{1,$bufferMaxRowLength} $textContent]
但这出奇的慢。
这是我的所有结果:
package require textutil
package require jitc 0.5.3
set str [string repeat a 1048576]
set bufferRowMaxLength 250
set split_cdef {
code {
#include <string.h>
OBJCMD(split) {
int code = TCL_OK;
Tcl_Obj** chunkobjs = NULL;
Tcl_Obj* res = NULL;
enum {A_cmd, A_STR, A_CHUNK, A_objc};
CHECK_ARGS_LABEL(finally, code, "str chunk");
int len;
const char* str = Tcl_GetStringFromObj(objv[A_STR], &len);
int chunk;
TEST_OK_LABEL(finally, code, Tcl_GetIntFromObj(interp, objv[A_CHUNK], &chunk));
const int chunks = (len + chunk - 1) / chunk;
chunkobjs = ckalloc(chunks * sizeof(chunkobjs[0]));
for (int i=0; i<chunks; i++) {
const int start = i * chunk;
const int end = start + chunk;
const int clen = end < len ? chunk : len - start;
chunkobjs[i] = Tcl_NewStringObj(str + start, clen);
}
replace_tclobj(&res, Tcl_NewListObj(chunks, chunkobjs));
Tcl_SetObjResult(interp, res);
finally:
replace_tclobj(&res, NULL);
if (chunkobjs) ckfree(chunkobjs);
return code;
}
OBJCMD(foreach_chunk) {
int code = TCL_OK;
enum {A_cmd, A_STR, A_CHUNK, A_VAR, A_SCRIPT, A_objc};
CHECK_ARGS_LABEL(finally, code, "str chunk var script");
int len;
const char* str = Tcl_GetStringFromObj(objv[A_STR], &len);
Tcl_WideInt chunk;
TEST_OK_LABEL(finally, code, Tcl_GetWideIntFromObj(interp, objv[A_CHUNK], &chunk));
const int chunks = (len + chunk - 1) / chunk;
for (int i=0; i<chunks; i++) {
const int start = i * chunk;
const int end = start + chunk;
const int clen = end < len ? chunk : len - start;
if (NULL == Tcl_ObjSetVar2(interp, objv[A_VAR], NULL, Tcl_NewStringObj(str + start, clen), TCL_LEAVE_ERR_MSG)) {
code = TCL_ERROR;
goto finally;
}
TEST_OK_LABEL(finally, code, Tcl_EvalObjEx(interp, objv[A_SCRIPT], 0));
}
finally:
return code;
}
}
}
jitc::bind jitc_split $split_cdef split
jitc::bind jitc_foreach_chunk $split_cdef foreach_chunk
set chunk .{1,$bufferRowMaxLength}
puts "regexp: [timerate {
set pieces [llength [regexp -all -inline $chunk $str]]
}], pieces: $pieces"
puts "splitn: [timerate {
set pieces [llength [::textutil::split::splitn $str $bufferRowMaxLength]]
}], pieces: $pieces"
puts "jitc split: [timerate {
set pieces [llength [jitc_split $str $bufferRowMaxLength]]
}], pieces: $pieces"
puts "jitc foreach_chunk: [timerate {
set pieces 0
jitc_foreach_chunk $str $bufferRowMaxLength chunk {
incr pieces
}
}], pieces: $pieces"
regexp: 348330.7 µs/# 3 # 2.871 #/sec 1044.992 net-ms, pieces: 4195
splitn: 567.176 µs/# 1764 # 1763.1 #/sec 1000.498 net-ms, pieces: 4195
jitc split: 235.065 µs/# 4255 # 4254.1 #/sec 1000.201 net-ms, pieces: 4195
jitc foreach_chunk: 463.043 µs/# 2160 # 2159.6 #/sec 1000.173 net-ms, pieces: 4195
regexp
的实施比其他实施慢得多,这表明其实施还有一些改进的空间,但存在一些不明显的低效率。
但对我来说,要点是,只要您具有将 Tcl 列表与块内容组装在一起的约束,那么
::textutil::split::splitn
(或等效的几行 Tcl)可能是正确的方法。在我的书中,C 实现增加的复杂性并不足以证明其合理性,而且几乎肯定会被对块本身所做的任何工作完全抵消。