Skip to content

Commit c3e8870

Browse files
karim-manaouilKarim Manaouil
authored andcommitted
filehash: implement per-bucket semaphores
thpchallenge-fio from mmtests [1] is frequently used for testing Linux kernel memory fragmentation [2]. thchallenge-fio writes a large number of 64K files inefficiently. On a system with 128GiB of RAM, it could create up to 100K files. A typical fio config looks like this [global] direct=0 ioengine=sync blocksize=4096 invalidate=0 fallocate=none create_on_open=1 [writer] nrfiles=98200 filesize=65536 readwrite=write numjobs=16 While testing this on a system with 128GiB of RAM and four NVMe SSDs on RAID0 that can achieve up to 25GB/s and at least 2M IOPS, it was observed that for the initial ~20 minutes, the test was running painfully slow at only ~2MiB/s with 16 fio threads. After some analysis, it was noticed that during this period, fio spends most of its time in looking up and adding the files from/to the file hash table, as shown in the fio report below 12.04% [.] __lookup_file_hash fio - - | --9.94%--__lookup_file_hash | --7.17%--lookup_file_hash generic_open_file td_io_open_file get_io_u thread_main run_threads fio_backend main 5.96% [.] strcmp@plt fio - - | --5.16%--__lookup_file_hash 56.38% [.] __strcmp_evex libc.so.6 - - | |--47.01%--__lookup_file_hash | | | |--35.25%--lookup_file_hash | | generic_open_file | | td_io_open_file | | get_io_u | | thread_main | | run_threads | | fio_backend | | main | | | --11.76%--add_file_hash | generic_open_file | td_io_open_file | get_io_u | thread_main | run_threads | fio_backend | main | --9.31%--__strcmp_evex | --5.31%--add_file_hash generic_open_file td_io_open_file get_io_u thread_main run_threads fio_backend main When the hash table is small, the threads spend a lot of time iterating through the files and comparing the filename. Increasing the size of the hash table helped significantly improve the performance by 20x, where now it only takes 1 minute or less. However that comes at the expense of more memory. Luckily, because the hash table has a single lock, it was observed that giving each bucket its own lock helped achieve the same performance improvment. Since this could potentially help more with multi-threading scenarios, in this patch, we consider this approach. Thus this patch implements a fio_sem per bucket. The filename is used to calculate which semaphore needs to be locked/unlocked. There is a global filename_list that also originally used the hash_lock for protection. We assign a random bucket with flist_bucket_name. [1] https://github.com/gormanm/mmtests/blob/master/configs/config-workload-thpchallenge-fio [2] https://lwn.net/Articles/770235/
1 parent de3d5e6 commit c3e8870

File tree

3 files changed

+51
-28
lines changed

3 files changed

+51
-28
lines changed

filehash.c

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,57 @@
1414
#define BLOOM_SIZE 16*1024*1024
1515

1616
static unsigned int file_hash_size = HASH_BUCKETS * sizeof(struct flist_head);
17+
static unsigned int sem_hash_size = HASH_BUCKETS * sizeof(struct fio_sem *);
1718

1819
static struct flist_head *file_hash;
19-
static struct fio_sem *hash_lock;
20+
static struct fio_sem **hash_locks;
2021
static struct bloom *file_bloom;
2122

2223
static unsigned short hash(const char *name)
2324
{
2425
return jhash(name, strlen(name), 0) & HASH_MASK;
2526
}
2627

27-
void fio_file_hash_lock(void)
28+
static unsigned int file_bucket(const char *file_name, struct flist_head *bucket)
2829
{
29-
if (hash_lock)
30-
fio_sem_down(hash_lock);
30+
struct flist_head *head;
31+
32+
head = &file_hash[hash(file_name)];
33+
if (bucket)
34+
bucket = head;
35+
36+
return head - file_hash;
37+
}
38+
39+
void fio_file_hash_lock(const char *file_name)
40+
{
41+
unsigned int buckidx = file_bucket(file_name, NULL);
42+
43+
if (hash_locks)
44+
fio_sem_down(hash_locks[buckidx]);
3145
}
3246

33-
void fio_file_hash_unlock(void)
47+
void fio_file_hash_unlock(const char *file_name)
3448
{
35-
if (hash_lock)
36-
fio_sem_up(hash_lock);
49+
unsigned int buckidx = file_bucket(file_name, NULL);
50+
51+
if (hash_locks)
52+
fio_sem_up(hash_locks[buckidx]);
3753
}
3854

3955
void remove_file_hash(struct fio_file *f)
4056
{
41-
fio_sem_down(hash_lock);
57+
unsigned long buckidx = file_bucket(f->file_name, NULL);
58+
59+
fio_sem_down(hash_locks[buckidx]);
4260

4361
if (fio_file_hashed(f)) {
4462
assert(!flist_empty(&f->hash_list));
4563
flist_del_init(&f->hash_list);
4664
fio_file_clear_hashed(f);
4765
}
4866

49-
fio_sem_up(hash_lock);
67+
fio_sem_up(hash_locks[buckidx]);
5068
}
5169

5270
static struct fio_file *__lookup_file_hash(const char *name)
@@ -71,9 +89,9 @@ struct fio_file *lookup_file_hash(const char *name)
7189
{
7290
struct fio_file *f;
7391

74-
fio_sem_down(hash_lock);
92+
fio_file_hash_lock(name);
7593
f = __lookup_file_hash(name);
76-
fio_sem_up(hash_lock);
94+
fio_file_hash_unlock(name);
7795
return f;
7896
}
7997

@@ -86,15 +104,15 @@ struct fio_file *add_file_hash(struct fio_file *f)
86104

87105
INIT_FLIST_HEAD(&f->hash_list);
88106

89-
fio_sem_down(hash_lock);
107+
fio_file_hash_lock(f->file_name);
90108

91109
alias = __lookup_file_hash(f->file_name);
92110
if (!alias) {
93111
fio_file_set_hashed(f);
94112
flist_add_tail(&f->hash_list, &file_hash[hash(f->file_name)]);
95113
}
96114

97-
fio_sem_up(hash_lock);
115+
fio_file_hash_unlock(f->file_name);
98116
return alias;
99117
}
100118

@@ -107,18 +125,19 @@ void file_hash_exit(void)
107125
{
108126
unsigned int i, has_entries = 0;
109127

110-
fio_sem_down(hash_lock);
111-
for (i = 0; i < HASH_BUCKETS; i++)
128+
for (i = 0; i < HASH_BUCKETS; i++) {
129+
fio_sem_down(hash_locks[i]);
112130
has_entries += !flist_empty(&file_hash[i]);
113-
fio_sem_up(hash_lock);
131+
fio_sem_up(hash_locks[i]);
132+
fio_sem_remove(hash_locks[i]);
133+
}
114134

115135
if (has_entries)
116136
log_err("fio: file hash not empty on exit\n");
117137

118138
sfree(file_hash);
119139
file_hash = NULL;
120-
fio_sem_remove(hash_lock);
121-
hash_lock = NULL;
140+
hash_locks = NULL;
122141
bloom_free(file_bloom);
123142
file_bloom = NULL;
124143
}
@@ -128,10 +147,12 @@ void file_hash_init(void)
128147
unsigned int i;
129148

130149
file_hash = smalloc(file_hash_size);
150+
hash_locks = smalloc(sem_hash_size);
131151

132-
for (i = 0; i < HASH_BUCKETS; i++)
152+
for (i = 0; i < HASH_BUCKETS; i++) {
133153
INIT_FLIST_HEAD(&file_hash[i]);
154+
hash_locks[i] = fio_sem_init(FIO_SEM_UNLOCKED);
155+
}
134156

135-
hash_lock = fio_sem_init(FIO_SEM_UNLOCKED);
136157
file_bloom = bloom_new(BLOOM_SIZE);
137158
}

filehash.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ extern void file_hash_exit(void);
88
extern struct fio_file *lookup_file_hash(const char *);
99
extern struct fio_file *add_file_hash(struct fio_file *);
1010
extern void remove_file_hash(struct fio_file *);
11-
extern void fio_file_hash_lock(void);
12-
extern void fio_file_hash_unlock(void);
11+
extern void fio_file_hash_lock(const char *);
12+
extern void fio_file_hash_unlock(const char *);
1313
extern bool file_bloom_exists(const char *, bool);
1414

1515
#endif

filesetup.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include <linux/falloc.h>
2222
#endif
2323

24+
static const char *flist_bucket_name = "flist_bucket";
25+
2426
static FLIST_HEAD(filename_list);
2527

2628
/*
@@ -1701,9 +1703,9 @@ static bool is_already_allocated(const char *fname)
17011703
{
17021704
bool ret;
17031705

1704-
fio_file_hash_lock();
1706+
fio_file_hash_lock(fname);
17051707
ret = __is_already_allocated(fname, false);
1706-
fio_file_hash_unlock();
1708+
fio_file_hash_unlock(fname);
17071709

17081710
return ret;
17091711
}
@@ -1715,12 +1717,12 @@ static void set_already_allocated(const char *fname)
17151717
fn = malloc(sizeof(struct file_name));
17161718
fn->filename = strdup(fname);
17171719

1718-
fio_file_hash_lock();
1720+
fio_file_hash_lock(flist_bucket_name);
17191721
if (!__is_already_allocated(fname, true)) {
17201722
flist_add_tail(&fn->list, &filename_list);
17211723
fn = NULL;
17221724
}
1723-
fio_file_hash_unlock();
1725+
fio_file_hash_unlock(flist_bucket_name);
17241726

17251727
if (fn) {
17261728
free(fn->filename);
@@ -1736,15 +1738,15 @@ static void free_already_allocated(void)
17361738
if (flist_empty(&filename_list))
17371739
return;
17381740

1739-
fio_file_hash_lock();
1741+
fio_file_hash_lock(flist_bucket_name);
17401742
flist_for_each_safe(entry, tmp, &filename_list) {
17411743
fn = flist_entry(entry, struct file_name, list);
17421744
free(fn->filename);
17431745
flist_del(&fn->list);
17441746
free(fn);
17451747
}
17461748

1747-
fio_file_hash_unlock();
1749+
fio_file_hash_unlock(flist_bucket_name);
17481750
}
17491751

17501752
static struct fio_file *alloc_new_file(struct thread_data *td)

0 commit comments

Comments
 (0)