lib/ftl: write buffer batches

This patch adds the ftl_batch strucutre, which describes a single batch
of data to be written to the disk.  It's comprised of multiple write
buffer entries (the actual number depends on the write unit size of the
underyling device).

Additionally, a function responsible for filling out a batch was added.
It iterates over available IO channels and dequeues submitted write
buffer entries until a batch is completed.  The IO channel queue is
shifted, so that each time the function is called a subsequent IO
channel is used first, which guarantees that all channels are treated
fairly.

Change-Id: Ie61d8c6cb51d5c5175540c975447bc27590c5cb4
Signed-off-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/905
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Wojciech Malikowski <wojciech.malikowski@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
This commit is contained in:
Konrad Sztyber 2020-02-11 13:38:16 +01:00 committed by Tomasz Zawadzki
parent 1e8c9a4de2
commit 35fdf2dd9e
5 changed files with 259 additions and 11 deletions

View File

@ -193,9 +193,7 @@ ftl_acquire_wbuf_entry(struct ftl_io_channel *io_channel, int io_flags)
return entry; return entry;
} }
void ftl_release_wbuf_entry(struct ftl_wbuf_entry *entry); static void
void
ftl_release_wbuf_entry(struct ftl_wbuf_entry *entry) ftl_release_wbuf_entry(struct ftl_wbuf_entry *entry)
{ {
struct ftl_io_channel *io_channel = entry->ioch; struct ftl_io_channel *io_channel = entry->ioch;
@ -207,6 +205,93 @@ ftl_release_wbuf_entry(struct ftl_wbuf_entry *entry)
spdk_ring_enqueue(io_channel->free_queue, (void **)&entry, 1, NULL); spdk_ring_enqueue(io_channel->free_queue, (void **)&entry, 1, NULL);
} }
struct ftl_batch *ftl_get_next_batch(struct spdk_ftl_dev *dev);
struct ftl_batch *
ftl_get_next_batch(struct spdk_ftl_dev *dev)
{
struct ftl_batch *batch = dev->current_batch;
struct ftl_io_channel *ioch;
#define FTL_DEQUEUE_ENTRIES 128
struct ftl_wbuf_entry *entries[FTL_DEQUEUE_ENTRIES];
TAILQ_HEAD(, ftl_io_channel) ioch_queue;
size_t i, num_dequeued, num_remaining;
if (batch == NULL) {
batch = TAILQ_FIRST(&dev->free_batches);
if (spdk_unlikely(batch == NULL)) {
return NULL;
}
assert(TAILQ_EMPTY(&batch->entries));
assert(batch->num_entries == 0);
TAILQ_REMOVE(&dev->free_batches, batch, tailq);
}
/*
* Keep shifting the queue to ensure fairness in IO channel selection. Each time
* ftl_get_next_batch() is called, we're starting to dequeue write buffer entries from a
* different IO channel.
*/
TAILQ_INIT(&ioch_queue);
while (!TAILQ_EMPTY(&dev->ioch_queue)) {
ioch = TAILQ_FIRST(&dev->ioch_queue);
TAILQ_REMOVE(&dev->ioch_queue, ioch, tailq);
TAILQ_INSERT_TAIL(&ioch_queue, ioch, tailq);
num_remaining = dev->xfer_size - batch->num_entries;
while (num_remaining > 0) {
num_dequeued = spdk_ring_dequeue(ioch->submit_queue, (void **)entries,
spdk_min(num_remaining,
FTL_DEQUEUE_ENTRIES));
if (num_dequeued == 0) {
break;
}
for (i = 0; i < num_dequeued; ++i) {
batch->iov[batch->num_entries + i].iov_base = entries[i]->payload;
batch->iov[batch->num_entries + i].iov_len = FTL_BLOCK_SIZE;
TAILQ_INSERT_TAIL(&batch->entries, entries[i], tailq);
}
batch->num_entries += num_dequeued;
num_remaining -= num_dequeued;
}
if (num_remaining == 0) {
break;
}
}
TAILQ_CONCAT(&dev->ioch_queue, &ioch_queue, tailq);
if (batch->num_entries == dev->xfer_size) {
dev->current_batch = NULL;
} else {
dev->current_batch = batch;
batch = NULL;
}
return batch;
}
void ftl_release_batch(struct spdk_ftl_dev *dev, struct ftl_batch *batch);
void
ftl_release_batch(struct spdk_ftl_dev *dev, struct ftl_batch *batch)
{
struct ftl_wbuf_entry *entry;
while (!TAILQ_EMPTY(&batch->entries)) {
entry = TAILQ_FIRST(&batch->entries);
TAILQ_REMOVE(&batch->entries, entry, tailq);
ftl_release_wbuf_entry(entry);
}
batch->num_entries = 0;
TAILQ_INSERT_TAIL(&dev->free_batches, batch, tailq);
}
static void static void
ftl_io_cmpl_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) ftl_io_cmpl_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{ {

View File

@ -107,6 +107,17 @@ struct ftl_nv_cache {
pthread_spinlock_t lock; pthread_spinlock_t lock;
}; };
struct ftl_batch {
/* Queue of write buffer entries, can reach up to xfer_size entries */
TAILQ_HEAD(, ftl_wbuf_entry) entries;
/* Number of entries in the queue above */
uint32_t num_entries;
/* Index within spdk_ftl_dev.batch_array */
uint32_t index;
struct iovec *iov;
TAILQ_ENTRY(ftl_batch) tailq;
};
struct spdk_ftl_dev { struct spdk_ftl_dev {
/* Device instance */ /* Device instance */
struct spdk_uuid uuid; struct spdk_uuid uuid;
@ -209,6 +220,15 @@ struct spdk_ftl_dev {
TAILQ_HEAD(, ftl_io_channel) ioch_queue; TAILQ_HEAD(, ftl_io_channel) ioch_queue;
uint64_t num_io_channels; uint64_t num_io_channels;
/* Write buffer batches */
#define FTL_BATCH_COUNT 4096
struct ftl_batch batch_array[FTL_BATCH_COUNT];
/* Iovec buffer used by batches */
struct iovec *iov_buf;
/* Batch currently being filled */
struct ftl_batch *current_batch;
TAILQ_HEAD(, ftl_batch) free_batches;
/* Devices' list */ /* Devices' list */
STAILQ_ENTRY(spdk_ftl_dev) stailq; STAILQ_ENTRY(spdk_ftl_dev) stailq;
}; };

View File

@ -1195,13 +1195,33 @@ ftl_io_channel_destroy_cb(void *io_device, void *ctx)
static int static int
ftl_dev_init_io_channel(struct spdk_ftl_dev *dev) ftl_dev_init_io_channel(struct spdk_ftl_dev *dev)
{ {
struct ftl_batch *batch;
uint32_t i;
dev->ioch_array = calloc(dev->conf.max_io_channels, sizeof(*dev->ioch_array)); dev->ioch_array = calloc(dev->conf.max_io_channels, sizeof(*dev->ioch_array));
if (!dev->ioch_array) { if (!dev->ioch_array) {
SPDK_ERRLOG("Failed to allocate IO channel array\n"); SPDK_ERRLOG("Failed to allocate IO channel array\n");
return -1; return -1;
} }
dev->iov_buf = calloc(FTL_BATCH_COUNT, dev->xfer_size * sizeof(struct iovec));
if (!dev->iov_buf) {
SPDK_ERRLOG("Failed to allocate iovec buffer\n");
return -1;
}
TAILQ_INIT(&dev->free_batches);
TAILQ_INIT(&dev->ioch_queue); TAILQ_INIT(&dev->ioch_queue);
for (i = 0; i < FTL_BATCH_COUNT; ++i) {
batch = &dev->batch_array[i];
batch->iov = &dev->iov_buf[i * dev->xfer_size];
batch->num_entries = 0;
batch->index = i;
TAILQ_INIT(&batch->entries);
TAILQ_INSERT_TAIL(&dev->free_batches, batch, tailq);
}
dev->num_io_channels = 0; dev->num_io_channels = 0;
spdk_io_device_register(dev, ftl_io_channel_create_cb, ftl_io_channel_destroy_cb, spdk_io_device_register(dev, ftl_io_channel_create_cb, ftl_io_channel_destroy_cb,
@ -1345,6 +1365,7 @@ ftl_dev_free_sync(struct spdk_ftl_dev *dev)
assert(dev->num_io_channels == 0); assert(dev->num_io_channels == 0);
free(dev->ioch_array); free(dev->ioch_array);
free(dev->iov_buf);
free(dev->name); free(dev->name);
free(dev->bands); free(dev->bands);
free(dev->l2p); free(dev->l2p);

View File

@ -154,6 +154,7 @@ struct ftl_wbuf_entry {
bool valid; bool valid;
/* Lock that protects the entry from being evicted from the L2P */ /* Lock that protects the entry from being evicted from the L2P */
pthread_spinlock_t lock; pthread_spinlock_t lock;
TAILQ_ENTRY(ftl_wbuf_entry) tailq;
}; };
struct ftl_io_channel { struct ftl_io_channel {

View File

@ -94,7 +94,7 @@ channel_destroy_cb(void *io_device, void *ctx)
{} {}
static struct spdk_ftl_dev * static struct spdk_ftl_dev *
setup_device(uint32_t num_threads) setup_device(uint32_t num_threads, uint32_t xfer_size)
{ {
struct spdk_ftl_dev *dev; struct spdk_ftl_dev *dev;
struct _ftl_io_channel *_ioch; struct _ftl_io_channel *_ioch;
@ -121,6 +121,7 @@ setup_device(uint32_t num_threads)
SPDK_CU_ASSERT_FATAL(ioch->io_pool != NULL); SPDK_CU_ASSERT_FATAL(ioch->io_pool != NULL);
dev->conf = g_default_conf; dev->conf = g_default_conf;
dev->xfer_size = xfer_size;
dev->base_bdev_desc = (struct spdk_bdev_desc *)0xdeadbeef; dev->base_bdev_desc = (struct spdk_bdev_desc *)0xdeadbeef;
spdk_io_device_register(dev->base_bdev_desc, channel_create_cb, channel_destroy_cb, 0, NULL); spdk_io_device_register(dev->base_bdev_desc, channel_create_cb, channel_destroy_cb, 0, NULL);
@ -144,6 +145,7 @@ free_device(struct spdk_ftl_dev *dev)
free_threads(); free_threads();
free(dev->ioch_array); free(dev->ioch_array);
free(dev->iov_buf);
free(dev->ioch); free(dev->ioch);
free(dev); free(dev);
} }
@ -183,7 +185,7 @@ test_completion(void)
int req, status = 0; int req, status = 0;
size_t pool_size; size_t pool_size;
dev = setup_device(1); dev = setup_device(1, 16);
ioch = ftl_io_channel_get_ctx(dev->ioch); ioch = ftl_io_channel_get_ctx(dev->ioch);
pool_size = spdk_mempool_count(ioch->io_pool); pool_size = spdk_mempool_count(ioch->io_pool);
@ -225,7 +227,7 @@ test_alloc_free(void)
int parent_status = -1; int parent_status = -1;
size_t pool_size; size_t pool_size;
dev = setup_device(1); dev = setup_device(1, 16);
ioch = ftl_io_channel_get_ctx(dev->ioch); ioch = ftl_io_channel_get_ctx(dev->ioch);
pool_size = spdk_mempool_count(ioch->io_pool); pool_size = spdk_mempool_count(ioch->io_pool);
@ -271,7 +273,7 @@ test_child_requests(void)
int status[MAX_CHILDREN + 1], i; int status[MAX_CHILDREN + 1], i;
size_t pool_size; size_t pool_size;
dev = setup_device(1); dev = setup_device(1, 16);
ioch = ftl_io_channel_get_ctx(dev->ioch); ioch = ftl_io_channel_get_ctx(dev->ioch);
pool_size = spdk_mempool_count(ioch->io_pool); pool_size = spdk_mempool_count(ioch->io_pool);
@ -371,7 +373,7 @@ test_child_status(void)
int parent_status, child_status[2]; int parent_status, child_status[2];
size_t pool_size, i; size_t pool_size, i;
dev = setup_device(1); dev = setup_device(1, 16);
ioch = ftl_io_channel_get_ctx(dev->ioch); ioch = ftl_io_channel_get_ctx(dev->ioch);
pool_size = spdk_mempool_count(ioch->io_pool); pool_size = spdk_mempool_count(ioch->io_pool);
@ -458,7 +460,7 @@ test_multi_generation(void)
size_t pool_size; size_t pool_size;
int i, j; int i, j;
dev = setup_device(1); dev = setup_device(1, 16);
ioch = ftl_io_channel_get_ctx(dev->ioch); ioch = ftl_io_channel_get_ctx(dev->ioch);
pool_size = spdk_mempool_count(ioch->io_pool); pool_size = spdk_mempool_count(ioch->io_pool);
@ -583,7 +585,7 @@ test_io_channel_create(void)
struct ftl_io_channel *ftl_ioch; struct ftl_io_channel *ftl_ioch;
uint32_t ioch_idx; uint32_t ioch_idx;
dev = setup_device(g_default_conf.max_io_channels + 1); dev = setup_device(g_default_conf.max_io_channels + 1, 16);
ioch = spdk_get_io_channel(dev); ioch = spdk_get_io_channel(dev);
CU_ASSERT(ioch != NULL); CU_ASSERT(ioch != NULL);
@ -656,7 +658,7 @@ test_acquire_entry(void)
uint32_t num_entries, num_io_channels = 2; uint32_t num_entries, num_io_channels = 2;
uint32_t ioch_idx, entry_idx, tmp_idx; uint32_t ioch_idx, entry_idx, tmp_idx;
dev = setup_device(num_io_channels); dev = setup_device(num_io_channels, 16);
num_entries = dev->conf.rwb_size / FTL_BLOCK_SIZE; num_entries = dev->conf.rwb_size / FTL_BLOCK_SIZE;
entries = calloc(num_entries * num_io_channels, sizeof(*entries)); entries = calloc(num_entries * num_io_channels, sizeof(*entries));
@ -798,6 +800,123 @@ test_acquire_entry(void)
free_device(dev); free_device(dev);
} }
static void
test_submit_batch(void)
{
struct spdk_ftl_dev *dev;
struct spdk_io_channel **_ioch_array;
struct ftl_io_channel **ioch_array;
struct ftl_wbuf_entry *entry;
struct ftl_batch *batch;
uint32_t num_io_channels = 16;
uint32_t ioch_idx, tmp_idx, entry_idx;
uint64_t ioch_bitmap;
size_t num_entries;
dev = setup_device(num_io_channels, num_io_channels);
_ioch_array = calloc(num_io_channels, sizeof(*_ioch_array));
SPDK_CU_ASSERT_FATAL(_ioch_array != NULL);
ioch_array = calloc(num_io_channels, sizeof(*ioch_array));
SPDK_CU_ASSERT_FATAL(ioch_array != NULL);
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
set_thread(ioch_idx);
_ioch_array[ioch_idx] = spdk_get_io_channel(dev);
SPDK_CU_ASSERT_FATAL(_ioch_array[ioch_idx] != NULL);
ioch_array[ioch_idx] = ftl_io_channel_get_ctx(_ioch_array[ioch_idx]);
poll_threads();
}
/* Make sure the IO channels are not starved and entries are popped in RR fashion */
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
set_thread(ioch_idx);
for (entry_idx = 0; entry_idx < dev->xfer_size; ++entry_idx) {
entry = ftl_acquire_wbuf_entry(ioch_array[ioch_idx], 0);
SPDK_CU_ASSERT_FATAL(entry != NULL);
num_entries = spdk_ring_enqueue(ioch_array[ioch_idx]->submit_queue,
(void **)&entry, 1, NULL);
CU_ASSERT(num_entries == 1);
}
}
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
for (tmp_idx = 0; tmp_idx < ioch_idx; ++tmp_idx) {
set_thread(tmp_idx);
while (spdk_ring_count(ioch_array[tmp_idx]->submit_queue) < dev->xfer_size) {
entry = ftl_acquire_wbuf_entry(ioch_array[tmp_idx], 0);
SPDK_CU_ASSERT_FATAL(entry != NULL);
num_entries = spdk_ring_enqueue(ioch_array[tmp_idx]->submit_queue,
(void **)&entry, 1, NULL);
CU_ASSERT(num_entries == 1);
}
}
set_thread(ioch_idx);
batch = ftl_get_next_batch(dev);
SPDK_CU_ASSERT_FATAL(batch != NULL);
TAILQ_FOREACH(entry, &batch->entries, tailq) {
CU_ASSERT(entry->ioch == ioch_array[ioch_idx]);
}
ftl_release_batch(dev, batch);
CU_ASSERT(spdk_ring_count(ioch_array[ioch_idx]->free_queue) ==
ioch_array[ioch_idx]->num_entries);
}
for (ioch_idx = 0; ioch_idx < num_io_channels - 1; ++ioch_idx) {
batch = ftl_get_next_batch(dev);
SPDK_CU_ASSERT_FATAL(batch != NULL);
ftl_release_batch(dev, batch);
}
/* Make sure the batch can be built from entries from any IO channel */
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
set_thread(ioch_idx);
entry = ftl_acquire_wbuf_entry(ioch_array[ioch_idx], 0);
SPDK_CU_ASSERT_FATAL(entry != NULL);
num_entries = spdk_ring_enqueue(ioch_array[ioch_idx]->submit_queue,
(void **)&entry, 1, NULL);
CU_ASSERT(num_entries == 1);
}
batch = ftl_get_next_batch(dev);
SPDK_CU_ASSERT_FATAL(batch != NULL);
ioch_bitmap = 0;
TAILQ_FOREACH(entry, &batch->entries, tailq) {
ioch_bitmap |= 1 << entry->ioch->index;
}
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
CU_ASSERT((ioch_bitmap & (1 << ioch_array[ioch_idx]->index)) != 0);
}
ftl_release_batch(dev, batch);
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
CU_ASSERT(spdk_ring_count(ioch_array[ioch_idx]->free_queue) ==
ioch_array[ioch_idx]->num_entries);
}
for (ioch_idx = 0; ioch_idx < num_io_channels; ++ioch_idx) {
set_thread(ioch_idx);
spdk_put_io_channel(_ioch_array[ioch_idx]);
}
poll_threads();
free(_ioch_array);
free(ioch_array);
free_device(dev);
}
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
@ -829,6 +948,8 @@ main(int argc, char **argv)
test_io_channel_create) == NULL test_io_channel_create) == NULL
|| CU_add_test(suite, "test_acquire_entry", || CU_add_test(suite, "test_acquire_entry",
test_acquire_entry) == NULL test_acquire_entry) == NULL
|| CU_add_test(suite, "test_submit_batch",
test_submit_batch) == NULL
) { ) {
CU_cleanup_registry(); CU_cleanup_registry();