block: Anticipatory I/O scheduler genetic plugin This plugin shows a worthwhile performance increase with most workloads averaging an 8% improvement. The implementation is fairly straightforward with the modification of the parameters as_general_set_child_genes. Signed-off-by: Jake Moilanen --- block/Kconfig.iosched | 9 + block/as-iosched.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 361 insertions(+), 1 deletion(-) Index: linux-rc/block/Kconfig.iosched =================================================================== --- linux-rc.orig/block/Kconfig.iosched +++ linux-rc/block/Kconfig.iosched @@ -66,4 +66,13 @@ config DEFAULT_IOSCHED default "cfq" if DEFAULT_CFQ default "noop" if DEFAULT_NOOP +config GENETIC_IOSCHED_AS + bool "Genetic Anticipatory I/O scheduler (EXPERIMENTAL)" + depends on IOSCHED_AS && GENETIC_LIB && EXPERIMENTAL + default n + ---help--- + This will use a genetic algorithm to tweak the tunables of the + anticipatory scheduler autonomically and will adapt tunables + depending on the present workload. + endmenu Index: linux-rc/block/as-iosched.c =================================================================== --- linux-rc.orig/block/as-iosched.c +++ linux-rc/block/as-iosched.c @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #define REQ_SYNC 1 #define REQ_ASYNC 0 @@ -64,6 +67,8 @@ */ #define MAX_THINKTIME (HZ/50UL) +unsigned long max_thinktime = MAX_THINKTIME; + /* Bits in as_io_context.state */ enum as_io_states { AS_TASK_RUNNING=0, /* Process has not exited */ @@ -80,6 +85,109 @@ enum anticipation_status { * or timed out */ }; +#ifdef CONFIG_GENETIC_IOSCHED_AS + +struct disk_stats_snapshot * as_stats_snapshot; + +extern void disk_stats_snapshot(phenotype_t * pt); +#ifdef CONFIG_FINGERPRINTING +extern void disk_get_fingerprint(phenotype_t * pt); +extern void disk_update_fingerprint(phenotype_t * pt); +extern void * as_create_genes(phenotype_t * pt); +#endif + +static void as_num_ops_create_child(genetic_child_t * child); +static void as_throughput_create_child(genetic_child_t * child); +static void as_latency_create_child(genetic_child_t * child); +static void as_general_create_child(genetic_child_t * child); + +static void as_general_set_child_genes(void * in_genes); + +static void as_num_ops_calc_fitness(genetic_child_t * child); +static void as_throughput_calc_fitness(genetic_child_t * child); +static void as_latency_calc_fitness(genetic_child_t * child); + +static void as_general_calc_post_fitness(phenotype_t * in_pt); + +static void as_shift_mutation_rate(phenotype_t * in_pt); + +struct genetic_ops as_num_ops_genetic_ops = { + .create_child = as_num_ops_create_child, + .calc_fitness = as_num_ops_calc_fitness, +}; + +struct genetic_ops as_throughput_genetic_ops = { + .create_child = as_throughput_create_child, + .calc_fitness = as_throughput_calc_fitness, +}; + +struct genetic_ops as_latency_genetic_ops = { + .create_child = as_latency_create_child, + .calc_fitness = as_latency_calc_fitness, +}; + +struct genetic_ops as_general_genetic_ops = { + .create_child = as_general_create_child, + .set_child_genes = as_general_set_child_genes, + .combine_genes = genetic_generic_combine_genes, + .mutate_child = genetic_generic_mutate_child, + .calc_post_fitness = as_general_calc_post_fitness, + .take_snapshot = disk_stats_snapshot, + .shift_mutation_rate = as_shift_mutation_rate, +#ifdef CONFIG_FINGERPRINTING + .get_fingerprint = disk_get_fingerprint, + .update_fingerprint = disk_update_fingerprint, + .create_top_genes = as_create_genes, + .top_fitness_show = fingerprint_top_fitness_show, + .snapshot_show = fingerprint_snapshot_show, + .state_show = fingerprint_state_show, +#endif +}; + +#define AS_NUM_CHILDREN 8 + +#define AS_NUM_OPS_UID 1 +#define AS_NUM_OPS_NUM_GENES 0 + +#define AS_THROUGHPUT_UID 2 +#define AS_THROUGHPUT_NUM_GENES 0 + +#define AS_LATENCY_UID 4 +#define AS_LATENCY_NUM_GENES 0 + +#define AS_GENERAL_UID (AS_NUM_OPS_UID | AS_THROUGHPUT_UID | AS_LATENCY_UID) +#define AS_GENERAL_NUM_GENES 7 + +struct as_genes { + unsigned long read_expire; + unsigned long write_expire; + unsigned long read_batch_expire; + unsigned long write_batch_expire; + unsigned long antic_expire; + unsigned long max_thinktime; + unsigned long nr_requests; +}; + +#define AS_TUNABLE(var, name) GENETIC_TUNABLE(var, name) + +gene_param_t as_gene_param[AS_GENERAL_NUM_GENES] = { + { "read_expire", HZ/16, 3*HZ/16, default_read_expire, 0}, + { "write_expire", HZ/8, 3*HZ/8, default_write_expire, 0}, + { "read_batch_expire", HZ/4, 3*HZ/4, default_read_batch_expire, 0}, + { "write_batch_expire", HZ/16, 3*HZ/16, default_write_batch_expire, 0}, +// { "default_antic_expire", HZ/300, HZ/100, default_antic_expire, 0}, + { "default_antic_expire", 0, HZ/100, default_antic_expire, 0}, + { "max_thinktime", HZ/100, 3*HZ/100, MAX_THINKTIME, 0}, + { "nr_requests", BLKDEV_MIN_RQ, BLKDEV_MAX_RQ*30, BLKDEV_MAX_RQ, genetic_generic_iterative_mutate_gene} +}; + +extern long long disk_num_ops_calc_fitness(genetic_child_t * child); +extern long long disk_throughput_calc_fitness(genetic_child_t * child); +extern long long disk_latency_calc_fitness(genetic_child_t * child); + +LIST_HEAD(as_data_list); +#endif + struct as_data { /* * run time data @@ -131,6 +239,9 @@ struct as_data { unsigned long fifo_expire[2]; unsigned long batch_expire[2]; unsigned long antic_expire; +#ifdef CONFIG_GENETIC_IOSCHED_AS + struct list_head data_list; +#endif }; #define list_entry_fifo(ptr) list_entry((ptr), struct as_rq, fifo) @@ -730,7 +841,7 @@ static void as_update_iohist(struct as_d if (test_bit(AS_TASK_IORUNNING, &aic->state) && in_flight == 0) { thinktime = jiffies - aic->last_end_request; - thinktime = min(thinktime, MAX_THINKTIME-1); + thinktime = min(thinktime, max_thinktime-1); } as_update_thinktime(ad, aic, thinktime); @@ -1628,6 +1739,11 @@ static void as_exit_queue(elevator_t *e) mempool_destroy(ad->arq_pool); put_io_context(ad->io_context); + +#ifdef CONFIG_GENETIC_IOSCHED_AS + list_del(&ad->data_list); +#endif + kfree(ad->hash); kfree(ad); } @@ -1690,6 +1806,10 @@ static void *as_init_queue(request_queue if (ad->write_batch_count < 2) ad->write_batch_count = 2; +#ifdef CONFIG_GENETIC_IOSCHED_AS + list_add_tail(&ad->data_list, &as_data_list); +#endif + return ad; } @@ -1805,6 +1925,9 @@ static struct elevator_type iosched_as = static int __init as_init(void) { int ret; +#ifdef CONFIG_GENETIC_IOSCHED_AS + genetic_t * genetic = 0; +#endif arq_pool = kmem_cache_create("as_arq", sizeof(struct as_rq), 0, 0, NULL, NULL); @@ -1813,6 +1936,36 @@ static int __init as_init(void) ret = elv_register(&iosched_as); if (!ret) { + +#ifdef CONFIG_GENETIC_IOSCHED_AS + as_stats_snapshot = (struct disk_stats_snapshot *)kmalloc(sizeof(struct disk_stats_snapshot), GFP_KERNEL); + if (!as_stats_snapshot) + panic("as: failed to malloc enough space"); + + + ret = genetic_init(&genetic, AS_NUM_CHILDREN, 2 * HZ, 1, "as-ioscheduler"); + if (ret) + panic("as: failed to init genetic lib"); + + if(genetic_register_phenotype(genetic, &as_num_ops_genetic_ops, AS_NUM_CHILDREN, + "num_ops", AS_NUM_OPS_NUM_GENES, AS_NUM_OPS_UID) == NULL) + panic("as: failed to register num_ops phenotype"); + + if(genetic_register_phenotype(genetic, &as_throughput_genetic_ops, AS_NUM_CHILDREN, + "throughput", AS_THROUGHPUT_NUM_GENES, AS_THROUGHPUT_UID) == NULL) + panic("as: failed to register throughput phenotype"); + + if(genetic_register_phenotype(genetic, &as_latency_genetic_ops, AS_NUM_CHILDREN, + "latency", AS_LATENCY_NUM_GENES, AS_LATENCY_UID) == NULL) + panic("as: failed to register latency phenotype"); + + if(genetic_register_phenotype(genetic, &as_general_genetic_ops, AS_NUM_CHILDREN, + "general", AS_GENERAL_NUM_GENES, AS_GENERAL_UID) == NULL) + panic("as: failed to register general phenotype"); + + genetic_start(genetic); +#endif + /* * don't allow AS to get unregistered, since we would have * to browse all tasks in the system and release their @@ -1839,6 +1992,204 @@ static void __exit as_exit(void) kmem_cache_destroy(arq_pool); } +#ifdef CONFIG_GENETIC_IOSCHED_AS + +static void as_num_ops_create_child(genetic_child_t * child) +{ + BUG_ON(!child); + + child->genes = 0; + child->gene_param = 0; + child->num_genes = AS_NUM_OPS_NUM_GENES; + child->stats_snapshot = as_stats_snapshot; +} + +static void as_throughput_create_child(genetic_child_t * child) +{ + BUG_ON(!child); + + child->genes = 0; + child->gene_param = 0; + child->num_genes = AS_THROUGHPUT_NUM_GENES; + child->stats_snapshot = as_stats_snapshot; +} + +static void as_latency_create_child(genetic_child_t * child) +{ + BUG_ON(!child); + + child->genes = 0; + child->gene_param = 0; + child->num_genes = AS_LATENCY_NUM_GENES; + child->stats_snapshot = as_stats_snapshot; +} + +/* need to create the genes for the child */ +static void as_general_create_child(genetic_child_t * child) +{ + BUG_ON(!child); + + child->genes = (void *)kmalloc(sizeof(struct as_genes), GFP_KERNEL); + if (!child->genes) + panic("as_general_create_child: error mallocing space"); + + child->gene_param = as_gene_param; + child->num_genes = AS_GENERAL_NUM_GENES; + child->stats_snapshot = as_stats_snapshot; + + genetic_create_child_spread(child, AS_NUM_CHILDREN-1); + + ((struct as_genes *)child->genes)->nr_requests = BLKDEV_MAX_RQ; +} + +static void as_shift_mutation_rate(phenotype_t * in_pt) +{ + struct list_head * p; + genetic_t * genetic = to_phenotype_genetic(in_pt); + phenotype_t * pt; + int count = 0; + long rate = 0; + + list_for_each(p, &genetic->phenotypes.list) { + pt = to_phenotype(to_kobj(p)); + + /* Look at everyone else that contributes to this + phenotype */ + if (pt->uid & AS_GENERAL_UID && pt->uid != AS_GENERAL_UID) { + + switch (pt->uid) { + case AS_NUM_OPS_UID: + case AS_THROUGHPUT_UID: + case AS_LATENCY_UID: + rate += pt->mutation_rate; + count++; + break; + default: + BUG(); + } + } + } + + /* If we are a general phenotype that is made up of other + phenotypes then we take the average */ + if (count) + in_pt->mutation_rate = (rate / count); + else + BUG(); +} + +static void as_general_set_child_genes(void * in_genes) +{ + struct as_genes * genes = (struct as_genes *)in_genes; + struct list_head * d; + struct as_data * ad; + + list_for_each(d, &as_data_list) { + ad = list_entry(d, struct as_data, data_list); + ad->fifo_expire[REQ_SYNC] = genes->read_expire; + ad->fifo_expire[REQ_ASYNC] = genes->write_expire; + ad->antic_expire = genes->antic_expire; + + if (genes->read_batch_expire > genes->write_expire) + genes->read_batch_expire = genes->write_expire; + ad->batch_expire[REQ_SYNC] = genes->read_batch_expire; + + if (genes->write_batch_expire > genes->read_expire) + genes->write_batch_expire = genes->read_expire; + ad->batch_expire[REQ_ASYNC] = genes->write_batch_expire; + + ad->q->nr_requests = genes->nr_requests; + } + max_thinktime = genes->max_thinktime; + +} + +static void as_num_ops_calc_fitness(genetic_child_t * child) +{ + child->fitness = disk_num_ops_calc_fitness(child); +} + +static void as_throughput_calc_fitness(genetic_child_t * child) +{ + child->fitness = disk_throughput_calc_fitness(child); +} + +static void as_latency_calc_fitness(genetic_child_t * child) +{ + child->fitness = disk_latency_calc_fitness(child); +} + +/* Make the general the one that takes into account all the fitness + * routines, since these are the common genes that effect everything. + */ +static void as_general_calc_post_fitness(phenotype_t * in_pt) +{ + struct list_head * p; + phenotype_t * pt; + genetic_t * genetic = to_phenotype_genetic(in_pt); + int ranking[AS_NUM_CHILDREN]; + int weight = 1; + int i; + + memset(ranking, 0, sizeof(ranking)); + + list_for_each(p, &genetic->phenotypes.list) { + pt = to_phenotype(to_kobj(p)); + + /* Look at everyone else that contributes to this + phenotype */ + if (pt->uid & AS_GENERAL_UID && pt->uid != AS_GENERAL_UID) { + + switch (pt->uid) { + case AS_NUM_OPS_UID: + weight = 2; + break; + case AS_THROUGHPUT_UID: + weight = 2; + break; + case AS_LATENCY_UID: + weight = 1; + break; + default: + BUG(); + } + + for (i = 0; i < pt->num_children; i++) + ranking[pt->child_ranking[i]->id] += (i * weight); + } + } + + for (i = 0; i < in_pt->num_children; i++) + in_pt->child_ranking[i]->fitness = ranking[i]; + +} + +#ifdef CONFIG_FINGERPRINTING +void * as_create_genes(phenotype_t * pt) +{ + struct as_genes * genes = (void *)kmalloc(sizeof(struct as_genes), GFP_KERNEL); + if (!genes) { + printk(KERN_ERR "as_create_genes: unable to alloc space\n"); + return 0; + } + + /* at some point...make these intelligent depending on what + * the workload is + */ + genes->read_expire = default_read_expire; + genes->write_expire = default_write_expire; + genes->read_batch_expire = default_read_batch_expire; + genes->write_batch_expire = default_write_batch_expire; + genes->antic_expire = default_antic_expire; + genes->max_thinktime = MAX_THINKTIME; + genes->nr_requests = BLKDEV_MAX_RQ; + + return (void *)genes; +} +#endif /* CONFIG_FINGERPRINTING */ + +#endif + module_init(as_init); module_exit(as_exit);