博客重载记录

前言: 有时候看了一些比较好的文章,过几天就忘了,想想不如自己实现一遍博客代码或按博客结构自己写一遍,加深印象,但把别人的内容改个名字变成自己的博客,有点不太好,故全写在这个博客中,权当个人记录。

流控算法实现

参考文章:
流量控制-从原理到实现
面试官:来,年轻人!请手撸5种常见限流算法!

计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <bits/stdc++.h>
class Counter {
Counter(int max_request_number, int period) : request_number_(0), max_request_number_(max_request_number), las_update_time_(time(nullptr)), period_(period) {}

bool IsVaild() {
time_t now = time(nullptr);
if (now - las_update_time_ >= period_) { // 超过时间片,重置请求数
request_number_ = 1;
las_update_time_ = now;
} else { // 更新请求数
request_number_++;
}
return request_number_ > max_request_number_;
}

private:
int request_number_; // 当前请求数量
int max_request_number_; // 最大请求数量
time_t las_update_time_; // 上次更新时间
int period_; // 时间片长度
};

滑动窗口
变量定义与第一个博客稍有不同,没怎么懂以下这一句
now_ms - window_size_ - start_time_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
class SlidingWindow {
SlidingWindow(int window_size_sum, int max_req_num, int split_num) {
window_size_sum_ = window_size_sum;
max_req_num_ = max_req_num;
window_size_ = window_size_sum_ / split_num;
split_num_ = split_num;
counter_.resize(split_num_);
window_start_time_ = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

bool IsVaild() {
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); // 当前时间
int window_num = (now_ms - window_start_time_) / window_size_; // 经过了多少个窗口
MoveWindow(window_num); // 移动窗口
int sum = std::accumulate(counter_.begin(), counter_.end(), 0); // 计算窗口请求数总和
if (sum >= max_req_num_) { // 超出阈值,返回失败
return false;
}
counter_[index_]++; // 当前窗口请求数加一
return true;
}

void MoveWindow(int window_num) {
if (window_num == 0) {
return;
}
window_num = std::min(window_num, split_num_);
for (int i = 0; i < window_num; i++) { // 将跳过的窗口请求数设为0
index_ = (index_ + 1) % split_num_;
counter_[index_] = 0;
}
window_start_time_ += window_num * window_size_; // 更新窗口开始时间
}

private:
int window_size_sum_; // 窗口大小总和(ms)
int max_req_num_; // 窗口最大请求数总和
int window_size_; // 窗口大小
int split_num_; // 窗口数目
std::vector<int> counter_; // 各窗口请求数目
uint64_t window_start_time_; // 当前窗口开始时间
int index_; // 当前窗口索引
};

漏桶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <bits/stdc++.h>
using Request = int;
class LeakyBucket {
public:
LeakyBucket(int capacity, int rate) : capacity_(capacity), rate_(rate), left_water_(0), last_update_time_(time(nullptr)) {}

bool IsVaild() {
uint64_t now = time(nullptr);
left_water_ = std::max<int>(0, left_water_ - (now - last_update_time_) * rate_); // 更新剩余水量
last_update_time_ = now;
if (left_water_ >= capacity_) {
return false;
}
left_water_++;
return true;
}

private:
int capacity_; // 桶容量
int rate_; // 每秒漏掉的水量
int left_water_; // 剩余水量
uint64_t last_update_time_; // 上次更新时间
};

int main() {
std::mutex mu; // 保护请求队列
std::queue<Request> reqest_queue; // 请求队列
LeakyBucket controller(5, 10); // 桶容量为5,100ms漏一个
bool live_ = true; // 控制线程生命周期
auto poll = [&]() { // 隔100ms取一个请求
printf("enter poll thread\n");
while (live_) {
mu.lock();
if (!reqest_queue.empty()) {
Request req = reqest_queue.front();
reqest_queue.pop();
printf("%d request success\n", req);
}
mu.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
printf("leave poll thread\n");
};
auto push = [&]() { // 50-100ms生成请求插入队列
Request req = 0;
printf("enter push thread\n");
while (live_) {
req++;
bool res = controller.IsVaild();
if (res) {
mu.lock();
reqest_queue.emplace(req);
mu.unlock();
} else {
printf("%d request failed\n", req);
}
int sleep_time = rand() % 50 + 50;
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
}
printf("leave push thread\n");
};

std::thread poll_thread(poll);
std::thread push_thread(push);
std::this_thread::sleep_for(std::chrono::minutes(1)); // 执行1分钟
live_ = false;
poll_thread.join();
push_thread.join();
}

输出结果:

令牌环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <bits/stdc++.h>
class TokenBucket {
public:
TokenBucket(int capacity, int rate) : token_num_(0), capacity_(capacity), rate_(rate), last_update_time_(time(nullptr)) {}

bool IsVaild() {
time_t now = time(nullptr);
token_num_ = std::min<int>(capacity_, token_num_ + (now - last_update_time_) * rate_); // 更新令牌数目
last_update_time_ = now;
if (token_num_ <= 0) {
return false;
}
token_num_--;
return true;
}

private:
int token_num_; // 当前令牌数
int capacity_; // 令牌桶容量
int rate_; // 每秒生成的令牌数目
time_t last_update_time_; // 上次更新的时间
};

open系统调用流程

参考文章:走马观花: Linux 系统调用 open 七日游(一)
内核代码版本:4.19.279

linux系统调用简要介绍

操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序与硬件设置一个额外层具有很多优点。首先这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来;其次,这极大地提升了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性;最后,更重要的是这些接口使得程序更具有移植性,因为只要内核所提供的一组接口相同,那么在任一内核之上就可以正确地编译和执行程序。Unix系统通过向内核发出系统调用(system call)实现了用户态进程和硬件设备之间的大部分接口。

《深入理解linux内核》——系统调用

【纯干货】linux内核——系统调用原理及实现
一次系统调用的完整执行过程如下:

  1. 通过特定指令发出系统调用(int $0x80、sysenter、syscall)
  2. CPU从用户态切换到内核态,进行一些寄存器和环境设置
  3. 调用system_call内核函数,通过系统调用号获取对应的服务例程
  4. 调用系统调用处理例程
  5. 使用特定指令从系统调用返回用户态(iret、sysexit、sysret)

系统调用号定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# arch/x86/entry/syscalls/syscall_64.tbl
#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0 common read __x64_sys_read
1 common write __x64_sys_write
2 common open __x64_sys_open
3 common close __x64_sys_close
4 common stat __x64_sys_newstat
5 common fstat __x64_sys_newfstat
6 common lstat __x64_sys_newlstat
7 common poll __x64_sys_poll
8 common lseek __x64_sys_lseek
9 common mmap __x64_sys_mmap
10 common mprotect __x64_sys_mprotect
11 common munmap __x64_sys_munmap
12 common brk __x64_sys_brk
13 64 rt_sigaction __x64_sys_rt_sigaction
14 common rt_sigprocmask __x64_sys_rt_sigprocmask

系统调用分派表(dispatch table) sys_call_table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// arch/x86/entry/syscall_64.c
// SPDX-License-Identifier: GPL-2.0
/* System call table for x86-64. */

#include <linux/linkage.h>
#include <linux/sys.h>
#include <linux/cache.h>
#include <asm/asm-offsets.h>
#include <asm/syscall.h>

/* this is a lie, but it does not hurt as sys_ni_syscall just returns -EINVAL */
extern asmlinkage long sys_ni_syscall(const struct pt_regs *);
#define __SYSCALL_64(nr, sym, qual) extern asmlinkage long sym(const struct pt_regs *);
#include <asm/syscalls_64.h>
#undef __SYSCALL_64

#define __SYSCALL_64(nr, sym, qual) [nr] = sym,

asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
/*
* Smells like a compiler bug -- it doesn't work
* when the & below is removed.
*/
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
};

// 相关定义
#ifdef CONFIG_X86_64
typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
#else
typedef asmlinkage long (*sys_call_ptr_t)(unsigned long, unsigned long,
unsigned long, unsigned long,
unsigned long, unsigned long);

/*
* Non-implemented system calls get redirected here.
*/
asmlinkage long sys_ni_syscall(void)
{
return -ENOSYS;
}

系统调用最多6个额外参数(除系统调用号)

相关结构体与函数声明

1
2
3
4
int open(const char * pathname, int flags, mode_t mode);
pathname: 打开文件的路径名
flags: 访问模式的一些标志
mode: 文件创建后的许可权位掩码

O_RDONLY :以只读方式打开文件
O_WRONLY :以只写方式打开文件
O_RDWR :以可读可写方式打开文件
O_CREAT:如果 pathname 参数指向的文件不存在则创建此文件
O_DIRECTORY :如果 pathname 参数指向的不是一个目录,则调用 open 失败
O_EXCL :此标志一般结合 O_CREAT 标志一起使用,用于专门创建文件
O_NOFOLLOW :如果 pathname 参数指向的是一个符号链接,将不对其进行解引用,直接返回错误
O_TRUNC :调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为 0
O_APPEND :调用 open 函数打开文件,当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾, 从文件末尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始
O_LARGEFILE:大型文件(文件大于2GB)
O_CLOEXEC:句柄在fork子进程后执行exec时就关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 进程描述符
struct task_struct {
/* Filesystem information: */
struct fs_struct *fs; // 文件系统信息

/* Open file information: */
struct files_struct *files; // 打开文件信息
...
};


struct path {
struct vfsmount *mnt;
struct dentry *dentry;
} __randomize_layout;

struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask; // 打开文件时候默认的文件访问权限
int in_exec;
struct path root, pwd; // 根目录与当前目录的目录项对象与安装点
} __randomize_layout;

/*
* The default fd array needs to be at least BITS_PER_LONG,
* as this is the granularity returned by copy_fdset().
*/
#define NR_OPEN_DEFAULT BITS_PER_LONG

struct fdtable {
unsigned int max_fds; // 最大容纳文件描述符数
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec; // exec时需关闭的文件描述符
unsigned long *open_fds; // bit为1表示相应位置被占用,表示有效的文件对象
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
/*
* Open file table structure
*/
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
bool resize_in_progress;
wait_queue_head_t resize_wait;

struct fdtable __rcu *fdt; // 指向对应的文件描述符表
struct fdtable fdtab; // 默认文件描述符表
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
unsigned int next_fd; // 所分配的最大描述符加一
// 文件描述符数小于NR_OPEN_DEFAULT时fdt直接引用以下对象
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu *fd_array[NR_OPEN_DEFAULT];
};

sys_open声明与定义
linux Kernel代码艺术——系统调用宏定义

1
2
3
4
5
6
7
8
9
10
// 函数声明
asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);
// 函数定义
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;

return do_sys_open(AT_FDCWD, filename, flags, mode);
}

SYSCALL_DEFINE3作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE_MAXARGS 6

#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
宏定义展开之后就成为:
asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);

宏展开后3表示系统参数个数,使用宏展开是为了将参数都当成long类型,进而执行寄存器的符号位扩展
asmlinkage
asmlinkage指定用堆栈传参数,用意是什么?寄存器不是更快吗
asmlinkage作用就是告诉编译器,函数参数不是用用寄存器来传递,而是用堆栈来传递的;
相关回答:

像楼上各位所说,用户调用syscall的时候,参数都是通过寄存器传进来的。中间内核由把所有的参数压栈了, 所以这个asmlinkage可以通过到gcc,恰好可以用正确的调用方式取到参数。
内核前面的那些统一处理很重要,这样后端真正的的syscall 实现函数就可以得到统一的调用方式了,而不是之间面对不同的abi。确实比较方便了。
不然每个syscall函数里面都要自己去处理不同abi,多很多重复代码。
当然也可以考虑在这个统一的处理的时候,把参数全部按照一定的规范放到寄存器。 但这个方法不能在所有的cpu架构上面都做的到。
我觉得这里的选择,“统一”要比这个“寄存器传参”要重要。 从用户切换到内核,要做大量的处理。相比较其他部分,这点参数的开销实在不算什么。

二分查找

写对二分查找不是套模板并往里面填空,需要仔细分析题意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include <bits/stdc++.h>
using namespace std;
int BinaryFindEqual(const vector<int>& data, int target) { // 等于
// 结果可能出现在[0,n-1]区间,不存在时返回-1
int low = 0;
int high = data.size() - 1;
while (low < high) {
int mid = (low + high) / 2; // 靠近low high都可以
if (data[mid] == target) {
return mid;
} else if (data[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
}
// 压缩区间至[low,high], low==high
if (data[low] == target) {
return low;
}
return -1;
}

int BinaryFindFirstGreaterEqual(const vector<int>& data, int target) { // 第一次大于等于
// 结果可能落在[0,n]
int low = 0;
int high = data.size();
while (low < high) {
int mid = (low + high) / 2; // 靠近low
if (data[mid] >= target) {
high = mid;
} else {
low = mid + 1;
}
}
// 压缩区间至[low,high], low==high
return low;
}

int BinaryFindFirstGreater(const vector<int>& data, int target) { // 第一次大于
// 结果可能落在[0,n]
int low = 0;
int high = data.size();
while (low < high) {
int mid = (low + high) / 2; // 靠近low
if (data[mid] > target) {
high = mid;
} else {
low = mid + 1;
}
}
// 压缩区间至[low,high], low==high
return low;
}

int BinaryFindLastLesserEqual(const vector<int>& data, int target) { // 最后一次小于等于
// 结果可能落在[-1,n-1]
if (data[0] > target) {
return -1;
}
int low = 0;
int high = data.size() - 1;
while (low < high) {
int mid = (low + high + 1) / 2; // 靠近high
if (data[mid] > target) {
high = mid - 1;
} else {
low = mid;
}
}
// 压缩区间至[low,high], low==high
return low;
}

int BinaryFindLastLesser(const vector<int>& data, int target) { // 最后一次小于
// 结果可能落在[-1,n-1]
if (data[0] >= target) {
return -1;
}
int low = 0;
int high = data.size() - 1;
while (low < high) {
int mid = (low + high + 1) / 2; // 靠近high
if (data[mid] >= target) {
high = mid - 1;
} else {
low = mid;
}
}
// 压缩区间至[low,high], low==high
return low;
}

int BinaryFindFirstEqual(const vector<int>& data, int target) { // 第一次等于
// 结果可能落在[0,n-1],不存在时返回-1
int low = 0;
int high = data.size() - 1;
while (low < high) {
int mid = (low + high) / 2; // 靠近low
if (data[mid] > target) {
high = mid - 1;
} else if (data[mid] < target) {
low = mid + 1;
} else {
high = mid;
}
}
// 压缩区间至[low,high], low==high
if (data[low] == target) {
return low;
}
return -1;
}

int BinaryFindLastEqual(const vector<int>& data, int target) { // 最后一次等于
// 结果可能落在[0,n-1],不存在时返回-1
int low = 0;
int high = data.size() - 1;
while (low < high) {
int mid = (low + high + 1) / 2; // 靠近high
if (data[mid] > target) {
high = mid - 1;
} else { // data[mid] < target时不将low设置为mid+1,避免low>high,当然也可以在末尾使用high避免这一问题
low = mid;
}
}
// 压缩区间至[low,high], low==high
if (data[low] == target) {
return low;
}
return -1;
}

int BinaryFindEqualCompare(const vector<int>& data, int target) { // 返回第一次相等的下标
for (int i = 0; i < data.size(); i++) {
if (data[i] == target) {
return i;
}
}
return -1;
}

int BinaryFindFirstGreaterEqualCompare(const vector<int>& data, int target) {
for (int i = 0; i < data.size(); i++) {
if (data[i] >= target) {
return i;
}
}
return data.size();
}

int BinaryFindFirstGreaterCompare(const vector<int>& data, int target) {
for (int i = 0; i < data.size(); i++) {
if (data[i] > target) {
return i;
}
}
return data.size();
}

int BinaryFindLastLesserEqualCompare(const vector<int>& data, int target) {
for (int i = data.size() - 1; i >= 0; i--) {
if (data[i] <= target) {
return i;
}
}
return -1;
}

int BinaryFindLastLesserCompare(const vector<int>& data, int target) {
for (int i = data.size() - 1; i >= 0; i--) {
if (data[i] < target) {
return i;
}
}
return -1;
}

int BinaryFindFirstEqualCompare(const vector<int>& data, int target) {
for (int i = 0; i < data.size(); i++) {
if (data[i] == target) {
return i;
}
}
return -1;
}

int BinaryFindLastEqualCompare(const vector<int>& data, int target) {
for (int i = data.size() - 1; i >= 0; i--) {
if (data[i] == target) {
return i;
}
}
return -1;
}

using FindFunc = function<int(const vector<int>&, int)>;
void TestBinaryFind(const vector<int>& data, const vector<int>& targets, FindFunc test_fn, FindFunc right_fn, string testname) {
for (int target : targets) {
int res1 = test_fn(data, target);
int res2 = right_fn(data, target);
if (res1 != res2) {
cout << "wrong anwer." << endl;
cout << "res1: " << res1 << " res2: " << res2 << endl;
}
}
cout << testname << " complete." << endl;
}

int main() {
vector<int> unique_data;
default_random_engine e;
uniform_int_distribution<int> u(1, 100);
e.seed(time(0));
for (int i = 5; i < 95; i++) {
if (u(e) > 50) {
unique_data.emplace_back(i);
}
}
vector<int> targets;
for (int i = 0; i <= 100; i++) {
targets.emplace_back(i);
}
cout << "unique data test:" << endl;
TestBinaryFind(unique_data, targets, BinaryFindEqual, BinaryFindEqualCompare, "BinaryFindEqual");
TestBinaryFind(unique_data, targets, BinaryFindFirstGreaterEqual, BinaryFindFirstGreaterEqualCompare, "BinaryFindFirstGreaterEqual");
TestBinaryFind(unique_data, targets, BinaryFindFirstGreater, BinaryFindFirstGreaterCompare, "BinaryFindFirstGreater");
TestBinaryFind(unique_data, targets, BinaryFindLastLesserEqual, BinaryFindLastLesserEqualCompare, "BinaryFindLastLesserEqual");
TestBinaryFind(unique_data, targets, BinaryFindLastLesser, BinaryFindLastLesserCompare, "BinaryFindLastLesser");

vector<int> repeat_data;
for (int i = 5; i < 95; i++) {
while (u(e) > 30) {
repeat_data.emplace_back(i);
}
}
cout << "repeat data test:" << endl;
TestBinaryFind(repeat_data, targets, BinaryFindFirstGreaterEqual, BinaryFindFirstGreaterEqualCompare, "BinaryFindFirstGreaterEqual");
TestBinaryFind(repeat_data, targets, BinaryFindFirstGreater, BinaryFindFirstGreaterCompare, "BinaryFindFirstGreater");
TestBinaryFind(repeat_data, targets, BinaryFindLastLesserEqual, BinaryFindLastLesserEqualCompare, "BinaryFindLastLesserEqual");
TestBinaryFind(repeat_data, targets, BinaryFindLastLesser, BinaryFindLastLesserCompare, "BinaryFindLastLesser");
TestBinaryFind(repeat_data, targets, BinaryFindFirstEqual, BinaryFindFirstEqualCompare, "BinaryFindFirstEqual");
TestBinaryFind(repeat_data, targets, BinaryFindLastEqual, BinaryFindLastEqualCompare, "BinaryFindLastEqual");
}