contributed by < TingL7
>
$ uname -r
4.14.0-041400-generic
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 142
Model name: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
Stepping: 10
CPU MHz: 2000.000
CPU max MHz: 4000.0000
CPU min MHz: 400.0000
BogoMIPS: 3984.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 8192K
NUMA node0 CPU(s): 0-7
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp
$ make
$ ./test_cpy
ternary_tree, loaded 259112 words in 0.108185 sec
Commands:
a add word to the tree
f find word in tree
s search words matching prefix
d delete word from the tree
q quit, freeing all data
choice: f
find word in tree: Taiwan
found Taiwan in 0.000002 sec.
choice: f
find word in tree: Chunghwa
Chunghwa not found.
choice: s
find words matching prefix (at least 1 char): Chung
Chung - searched prefix in 0.000008 sec
suggest[0] : Chunghwa,
choice: a
enter word to add: tii
tii - inserted in 0.000006 sec. (259113 words in tree)
choice: f
find word in tree: tii
found tii in 0.000002 sec.
choice: d
enter word to del: tii
deleting tii
deleted tii in 0.000010 sec
choice: q
f
找不到的東西,使用 s
居然找到了,多試了幾筆資料,推測 f
能夠查詢的只有國家,而無法查詢城市。test_cpy
、 test_ref
在功能測試上,結果相同。a
功能為在數中新增節點, d
為刪除節點。開啟 test_ref.c
INS
為 0 , DEL
為 1 。REF
為 0 , CPY
為 1 。
enum { INS, DEL, WRDMAX = 256, STKMAX = 512, LMAX = 1024 };
#define REF INS
#define CPY DEL
/* FIXME: insert reference to each string */
if (!tst_ins_del(&root, &p, INS, CPY)) {
/* FIXME: insert reference to each string */
res = tst_ins_del(&root, &p, INS, CPY);
/* FIXME: remove reference to each string */
res = tst_ins_del(&root, &p, DEL, CPY);
tst.c
查看 tst_ins_del
的內容cpy
,決定要幫字串 allocate storage 或 save pointer 只出現在 for 迴圈中:
void *tst_ins_del(tst_node **root, char *const *s, const int del, const int cpy)
{
...
/* if not duplicate, insert remaining chars into tree rooted at curr */
for (;;) {
...
/* Place nodes until end of the string, at end of stign allocate
* space for data, copy data as final eqkid, and return.
*/
if (*p++ == 0) {
if (cpy) { /* allocate storage for 's' */
const char *eqdata = strdup(*s);
if (!eqdata)
return NULL;
curr->eqkid = (tst_node *) eqdata;
return (void *) eqdata;
} else { /* save pointer to 's' (allocated elsewhere) */
curr->eqkid = (tst_node *) *s;
return (void *) *s;
}
}
pcurr = &(curr->eqkid);
}
}
CPY
改成 REF
,結果在 command s
、 q
會出錯 :choice: s
find words matching prefix (at least 1 char): Chung
Chung - searched prefix in 0.000009 sec
suggest[0] : Chung
suggest[1] : Chung
suggest[2] : Chung
suggest[3] : Chung
choice: q
*** Error in `./test_ref': free(): invalid pointer: 0x00007ffd1303a630 ***
======= Backtrace: =========
...
======= Memory map: ========
...
Aborted (core dumped)
tst_ins_del()
的參數 s
為讀入的 word
,在 save pointer 的情況下,直接將 tst 指向 s
,但 word
只是為了讀檔而暫存的地方,資料會不停的被洗掉,因此,有儲存指標,但指標的內容不停被更動。
while ((rtn = fscanf(fp, "%s", word)) != EOF) {
char *p = word;
/* FIXME: insert reference to each string */
if (!tst_ins_del(&root, &p, INS, REF)) {
fprintf(stderr, "error: memory exhausted, tst_insert.\n");
fclose(fp);
return 1;
}
idx++;
}
s
s
指令時,會呼叫 tst_search_prefix(const tst_node *root, const char *s, char **a, int *n, const int max)
,其中參數 a
是儲存 suggest
的變數,而在這個函數中,決定 a
的是一個遞迴函數
void tst_suggest(const tst_node *p,
const char c,
const size_t nchr,
char **a,
int *n,
const int max)
{
if (!p || *n == max)
return;
tst_suggest(p->lokid, c, nchr, a, n, max);
if (p->key)
tst_suggest(p->eqkid, c, nchr, a, n, max);
else if (*(((char *) p->eqkid) + nchr - 1) == c)
a[(*n)++] = (char *) p->eqkid;
tst_suggest(p->hikid, c, nchr, a, n, max);
}
a
直接取存放在 leaf 的值,也就是在建立 tst 時的 word
。但,很不幸的,之前在建立時,由於指向的內容是被修改過的,因此在使用 command s
時,建議值會出錯。tst.c
的前提下,要儲存指標,且內容不得更動,就要使每次進去 tst_ins_del()
中的 word
位址不同。a
) ,不適合直接寫死,而且直接寫死要考慮到有 259111 個城市 (word) ,宣告太大的陣列會 stack overflow ,因此以動態陣列的方式宣告比較適合。
char (*word_tst)[WRDMAX] = malloc(260000 * sizeof(*word_tst));
while ((rtn = fscanf(fp, "%s", word_tst[idx])) != EOF) {
char *p = word_tst[idx];
/* FIXME: insert reference to each string */
if (!tst_ins_del(&root, &p, INS, REF)) {
fprintf(stderr, "error: memory exhausted, tst_insert.\n");
fclose(fp);
return 1;
}
idx++;
}
...
case 'q':
tst_free_all(root);
free(word_tst);
return 0;
break;
choice: s
find words matching prefix (at least 1 char): Tain
Tain - searched prefix in 0.000018 sec
suggest[0] : Tain,
suggest[1] : Tainan,
suggest[2] : Taino,
suggest[3] : Tainter
suggest[4] : Taintrux,
choice: q
*** Error in `./test_ref': free(): invalid pointer: 0x00007f4cc9d7ef10 ***
======= Backtrace: =========
...
======= Memory map: ========
...
Aborted (core dumped)
s
沒問題,command q
還有錯誤。q
q
相關的程式碼很單純,主要就是在釋放記憶體。
case 'q':
tst_free_all(root);
free(word_tst);
return 0;
break;
tst.c
中,與釋放記憶體有關的函式有兩個:
/** free the ternary search tree rooted at p, data storage internal. */
void tst_free_all(tst_node *p)
{
if (!p)
return;
tst_free_all(p->lokid);
if (p->key)
tst_free_all(p->eqkid);
tst_free_all(p->hikid);
if (!p->key)
free(p->eqkid);
free(p);
}
/** free the ternary search tree rooted at p, data storage external. */
void tst_free(tst_node *p)
{
if (!p)
return;
tst_free(p->lokid);
if (p->key)
tst_free(p->eqkid);
tst_free(p->hikid);
free(p);
}
tst_free()
而非 tst_free_all()
。case 'q':
tst_free(root);
free(word_tst);
return 0;
break;
CPY
改成 REF
case 'a':
printf("enter word to add: ");
if (!fgets(word, sizeof word, stdin)) {
fprintf(stderr, "error: insufficient input.\n");
break;
}
rmcrlf(word);
p = word;
t1 = tvgetf();
/* FIXME: insert reference to each string */
res = tst_ins_del(&root, &p, INS, REF);
t2 = tvgetf();
if (res) {
idx++;
printf(" %s - inserted in %.6f sec. (%d words in tree)\n",
(char *) res, t2 - t1, idx);
} else
printf(" %s - already exists in list.\n", (char *) res);
break;
s
、 d
會出錯 :
choice: a
enter word to add: Tiz
Tiz - inserted in 0.000009 sec. (259113 words in tree)
choice: s
find words matching prefix (at least 1 char): Tiz
Tiz - searched prefix in 0.000021 sec
suggest[0] : Tiz
suggest[1] : Tiz
suggest[2] : Tiza,
suggest[3] : Tizapán
suggest[4] : Tizayuca,
suggest[5] : Tizi
suggest[6] : Tizi-n-Tleta,
suggest[7] : Tizimín,
suggest[8] : Tiznit,
suggest[9] : Tizzano
choice: d
enter word to del: Tiz
deleting Tiz
*** Error in `./test_ref': free(): invalid pointer: 0x00007ffe8b614100 ***
======= Backtrace: =========
...
======= Memory map: ========
...
Aborted (core dumped)
s
出錯的原因在於 insert 時失敗,與 FIXME 1 相同, 有 word 指向同一塊位址的問題。因此,仿造上面的作法,以 array 的方式儲存字串。d
出錯的原因為沒有正確釋放記憶體。
case 'a':
printf("enter word to add: ");
if (!fgets(word_tst[idx], sizeof word_tst[idx], stdin)) {
fprintf(stderr, "error: insufficient input.\n");
break;
}
rmcrlf(word_tst[idx]);
p = word_tst[idx];
t1 = tvgetf();
/* FIXME: insert reference to each string */
res = tst_ins_del(&root, &p, INS, REF);
t2 = tvgetf();
if (res) {
idx++;
printf(" %s - inserted in %.6f sec. (%d words in tree)\n",
(char *) res, t2 - t1, idx);
} else
printf(" %s - already exists in list.\n", (char *) res);
break;
choice: a
enter word to add: Tiz
Tiz - inserted in 0.000010 sec. (259113 words in tree)
choice: s
find words matching prefix (at least 1 char): Tiz
Tiz - searched prefix in 0.000022 sec
suggest[0] : Tiz
suggest[1] : Tiza,
suggest[2] : Tizapán
suggest[3] : Tizayuca,
suggest[4] : Tizi
suggest[5] : Tizi-n-Tleta,
suggest[6] : Tizimín,
suggest[7] : Tiznit,
suggest[8] : Tizzano
d
出錯的地方在 tst_ins_del()
中所呼叫的 tst_del_word()
return tst_del_word(root, curr, &stk, 1);
static void *tst_del_word(tst_node **root,
tst_node *node,
tst_stack *stk,
const int freeword)
{
tst_node *victim = node; /* begin deletion w/victim */
tst_node *parent = tst_stack_pop(stk); /* parent to victim */
if (!victim->refcnt) { /* if last occurrence */
if (!victim->key && freeword) /* check key is nul */
free(victim->eqkid); /* free string (data) */
...
if 'freeword = 0', node->eqkid is stored elsewhere and not freed
,但從上面這兩段程式碼可以發現 freeword 永遠為 1 ,因次要將 line 238 的 tst_del_word()
最後一個參數改為 cpy
。tst.c
)
return tst_del_word(root, curr, &stk, 238);
choice: a
enter word to add: Tiz
Tiz - inserted in 0.000009 sec. (259113 words in tree)
choice: d
enter word to del: Tiz
deleting Tiz
deleted Tiz in 0.000014 sec
word_tst
是由 idx
在控制,但在刪除之後, word_tst
實際上是沒有減少的,但是會作 idx--
因此在之後新增時,會覆蓋到原本 word_tst
中的資料。word_tst
的變數。
char (*word_tst)[WRDMAX] = malloc(260000 * sizeof(*word_tst));
int i = 0;
while ((rtn = fscanf(fp, "%s", word_tst[i])) != EOF) {
char *p = word_tst[i];
/* FIXME: insert reference to each string */
if (!tst_ins_del(&root, &p, INS, REF)) {
fprintf(stderr, "error: memory exhausted, tst_insert.\n");
fclose(fp);
return 1;
}
idx++;
i++;
}
case 'a':
printf("enter word to add: ");
if (!fgets(word_tst[i], sizeof word_tst[i], stdin)) {
fprintf(stderr, "error: insufficient input.\n");
break;
}
rmcrlf(word_tst[i]);
p = word_tst[i];
t1 = tvgetf();
/* FIXME: insert reference to each string */
res = tst_ins_del(&root, &p, INS, REF);
t2 = tvgetf();
if (res) {
i++;
idx++;
Makefile
以做出效能評比--bench
,作為 main 的參數。
--bench
會存放在 argv[1]./test_ref --bench s Tain
--bench
,可透過設立變數 (bench_flag) 來判斷。test_ref.c
及 test_cpy.c
都要設立。--bench
與一般 make ,不一樣的地方在於不用手動輸入資料,要自動的讀資料。先設定讀入的資料只有一筆 (s Tain) ,處理完之後,必須自動執行 command q
。int bench_flag = (agrc > 1 && !(strcmp(argv[1], "--bench"))?1:0;
...
for(;;)
char *p;
if(bench_flag){
if(bench_flag > 1)
strcpy(word, "q");
else{
strcpy(word, argv[2]);
bench_flag++;
}
}else{
printf(
"\nCommands:\n"
" a add word to the tree\n"
" f find word in tree\n"
" s search words matching prefix\n"
" d delete word from the tree\n"
" q quit, freeing all data\n\n"
"choice: ");
fgets(word, sizeof word, stdin);
}
p = NULL;
switch (*word) {
case 'a':
if(bench_flag)
strcpy(word_tst[i], argv[3]);
else{
printf("enter word to add: ");
if (!fgets(word_tst[i], sizeof word_tst[i], stdin)) {
fprintf(stderr, "error: insufficient input.\n");
break;
}
}
...
Makefile
)DATA = s Tain
bench: $(TEST)
perf stat --repeat 100 \
-e cache-misses,cache-references,instructions,cycles \
./test_cpy --bench $(DATA)
perf stat --repeat 100 \
-e cache-misses,cache-references,instructions,cycles \
./test_ref --bench $(DATA)
ternary_tree, loaded 259112 words in 0.116547 sec
Tain - searched prefix in 0.000003 sec, 5 suggests
Performance counter stats for './test_cpy --bench s Tain' (100 runs):
2,944,151 cache-misses:u # 37.957 % of all cache refs ( +- 0.21% )
7,756,549 cache-references:u ( +- 0.17% )
513,611,005 instructions:u # 1.35 insn per cycle ( +- 0.07% )
380,216,819 cycles:u ( +- 0.08% )
0.134732372 seconds time elapsed ( +- 0.18% )
ternary_tree, loaded 259112 words in 0.131607 sec
Tain - searched prefix in 0.000005 sec,5 suggests
Performance counter stats for './test_ref --bench s Tain' (100 runs):
3,644,486 cache-misses:u # 42.456 % of all cache refs ( +- 0.29% )
8,584,217 cache-references:u ( +- 0.12% )
483,558,386 instructions:u # 1.23 insn per cycle ( +- 0.00% )
394,352,557 cycles:u ( +- 0.12% )
0.168124774 seconds time elapsed ( +- 0.28% )
,
作為 token 且割字串。,
,
分辨iscountry
欄位
while ((fgets(str_ar, WRDMAX * 3, fp)) != NULL) {
str = str_ar;
strcpy(word, strsep(&str, ",\n"));
while (strcmp(word, "") != 0) {
if(word[0] == ' '){
int j;
for(j = 1;j < strlen(word);j++)
word_tst[i][j-1] = word[j];
word_tst[i][j-1] = '\0';
}else{
int j;
for(j = 0;j < strlen(word);j++)
word_tst[i][j] = word[j];
word_tst[i][j] = '\0';
}
char *p = word_tst[i];
/* FIXME: insert reference to each string */
if (!tst_ins_del(&root, &p, INS, REF)) {
fprintf(stderr, "error: memory exhausted, tst_insert.\n");
fclose(fp);
return 1;
}
idx++;
i++;
strcpy(word, strsep(&str, ",\n"));
}
}
ternary_tree, loaded 206849 words in 0.106141 sec
choice: f
find word in tree: Tainan
found Tainan in 0.000004 sec.
choice: f
find word in tree: Newcastle under Lyme
found Newcastle under Lyme in 0.000004 sec.
test_cpy
和 test_ref
的效能,並提出改善機制分析 prefix search
char search_word[PREFIX] = "";
int count = 0;
fp = fopen(IN_FILE, "r");
while(fscanf(fp, "%s", word) != EOF){
if(strlen(word) < 3) continue;
strncpy(search_word, word, PREFIX);
t1 = tvgetf();
tst_search_prefix(root, search_word, sgl, &sidx, LMAX);
t2 = tvgetf();
fprintf(fp_out2, "%d %.8f\n", count++, t2 - t1);
}
fclose(fp_out2);
信賴區間
$gnuplot
$stats "cpy.txt" using 2
$stats "ref.txt" using 2
* FILE: cpy.txt ref.txt
Records: 254073 254073
Out of range: 0 0
Invalid: 0 0
Blank: 0 0
Data Blocks: 1 1
* COLUMN:
Mean: 2.58959e-07 2.16105e-07
Std Dev: 2.15182e-07 2.57175e-07
Sample StdDev: 2.15183e-07 2.57176e-07
Skewness: 21.9973 139.0368
Kurtosis: 2635.6369 38465.3643
Avg Dev: 1.23026e-07 1.06763e-07
Sum: 0.0658 0.0549
Sum Sq.: 2.88026e-08 2.86697e-08
Mean Err.: 4.26901e-10 5.10211e-10
Std Dev Err.: 3.01865e-10 3.60773e-10
Skewness Err.: 0.0049 0.0049
Kurtosis Err.: 0.0097 0.0097
Minimum: 0.0000 [ 6] 0.000[ 11]
Maximum: 0.0000 [ 40470] 0.001[ 23637]
Quartile: 2.40000e-07 2.40000e-07
Median: 2.40000e-07 2.40000e-07
Quartile: 2.40000e-07 2.40000e-07
The Confidence Interval formula is
2.58959e-07
2.16105e-07
2.15182e-07
2.57175e-07
代入公式,得 :
CPY版本 prefix search 95% 信賴區間: 2.58959e-07 ± 8.3672502e-10 (sec)
REF版本 prefix search 95% 信賴區間: 2.16105e-07 ± 1.00001281e-9 (sec)
記憶體配置
node |
---|
node |
node |
leaf |
node |
node |
leaf |
leaves |
---|
node |
node |
node |
node |
node |
改善方法
tst_traverse_fn
函式tst_traverse_fn
function
/** tst_traverse_fn(), traverse tree calling 'fn' on each word.
* prototype for 'fn' is void fn(const void *, void *). data can
* be NULL if unused.
*/
void tst_traverse_fn(const tst_node *p,
void(fn)(const void *, void *),
void *data)
{
if (!p)
return;
tst_traverse_fn(p->lokid, fn, data);
if (p->key)
tst_traverse_fn(p->eqkid, fn, data);
else
fn(p, data);
tst_traverse_fn(p->hikid, fn, data);
}
void fn(const void *p, void *data)
{
++*(int *)data;
}
tst_traverse_fn(root, fn, &data);
printf("data: %d\n", data);
data: 85500
t1 = tvgetf();
tst_traverse_fn(root, fn, &data);
t2 = tvgetf();
printf("cpy time: %.6f\n", t2-t1);
cpy time: 0.015064
ref time: 0.015275
void fn(const void *p, void *data){
printf("%s\n", tst_get_string(p));
}
tst_traverse_fn(root, fn, &data);
...
Tufino,
Tuftonboro,
Tuganay,
Tugas,
Tugaya,
Tugbong,
Tugdan,
Tuggen,
Tuglie,
Tugolesskiy
Tugos,
Tuguegarao
Tugulym,
Tugun,
...