哈西表,大家都非常熟悉了,对于哈西查询算法,如果从cpu的角度看问题,会有些不同的感觉。 如下代码是找出我们匹配的一项
struct connection * lookup(struct bucket * head,unsigned int saddr, unsigned int daddr, unsigned short sport, unsigned short dport) { struct connection *conn = head->conn; for(; NULL != conn; conn = conn->next){ if(...) break; } return conn;
} 假设我们不希望有太多的内存被占用,或者哈西值总是无法满足我们的分布要求(如果效果很好,就没有必要看下去了:-)),所以让我们来考虑上面代码的效率。如果不考虑cache miss 的情况,那么就来考虑指令流水的问题。 现在我们所使用的指令预测大致为经典的 two-level predication, gshare的 hybrid predication, agree predication...他们都是以跳转指令的地址,作为基础。所以近乎每一次的查询几乎都会产生misspredication,产生延迟的时钟是pipeline的级数,为了解决这个问题,我使用了很笨的方法,就是为每一个桶产生一个相同的查询函数,简单的测试结果告诉我提高了27%。 代码如下:
conn = bucket ->lookup( saddr, daddr, sport, dport); (说实话上面实验仅仅来验证自己的观点,实际应用中同样会产生指令预测失败。因为我们不能奢望总是会找上次一样的地方(core2的指令预测,在上面的代码将使用loop counter ))
所以我将hash设计如下 struct list{ struct list *next; struct list *prev;
}; struct connection { struct list link; unsigned long saddr; unsigned long daddr; unsigned short sport; unsigned short dport; int state;
}; struct bucket { struct list head; struct connection *cache; }; 初始化之后bucket 与 conn 构成了一个双向闭环,linux这样的结构比比皆是。 上面代码希望使用公共的查询函数完成指令预测的效果减少指令预测失败带来的负面影响,bucket 中的 cache 存储着上次访问的连接,connection 中的state 有四个状态 00, 01, 10, 11, 初始值为0,当我们发现 cache 并不是我们的目标,那么就检查cache所指向的connection 中的状态值,如果<2向左查,否则向右查,如果左侧查不到,就查右侧,反之也一样。根据最后的结果+1(右侧找到)或者 -1(左侧找到),最大不超过3最小不超过0。以上的代码我测试过,某些情况下效果很好 提高 400%,有些时候提高 50%,最差情况没有提高,也没有衰退。 实际上我认为学习汇编的目的是为了了解cpu,
|