├── Makefile ├── script.sh ├── gen.py ├── common.hpp ├── README.md ├── page-info.h ├── plot-csv.py ├── testingmlp.cpp └── page-info.c /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -O3 -Wall -std=c++17 2 | CFLAGS = -O3 -Wall 3 | 4 | .PHONY = clean test 5 | 6 | .DELETE_ON_ERROR: 7 | 8 | testingmlp: testingmlp.o generated.o page-info.o 9 | $(CXX) $(CXXFLAGS) -o $@ $^ -lm 10 | 11 | ifneq ($(GENERATE), 0) 12 | generated.cpp : gen.py 13 | python3 gen.py > generated.cpp 14 | endif 15 | 16 | %.o : %.cpp 17 | $(CXX) $(CXXFLAGS) -c $< 18 | 19 | %.o : %.c 20 | $(CC) $(CFLAGS) -c $< 21 | 22 | page-info.o : page-info.h 23 | 24 | generated.o testingmlp.o : common.hpp 25 | 26 | test: testingmlp 27 | ./testingmlp 28 | 29 | clean: 30 | rm -f testingmlp generated.cpp *.o 31 | -------------------------------------------------------------------------------- /script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! [ $(id -u) = 0 ]; then 3 | echo "The script need to be run as root." >&2 4 | exit 1 5 | fi 6 | 7 | if [ $SUDO_USER ]; then 8 | real_user=$SUDO_USER 9 | else 10 | real_user=$(whoami) 11 | fi 12 | 13 | origval=$(sudo cat /sys/kernel/mm/transparent_hugepage/enabled) 14 | sudo -u $real_user echo $origval 15 | set -e 16 | function cleanup { 17 | echo "Restauring hugepages to madvise" 18 | echo "madvise" > /sys/kernel/mm/transparent_hugepage/enabled 19 | } 20 | trap cleanup EXIT 21 | 22 | for mode in "always" "never" ; do 23 | sudo -u $real_user echo "mode: " $mode 24 | echo $mode > /sys/kernel/mm/transparent_hugepage/enabled 25 | echo $(sudo cat /sys/kernel/mm/transparent_hugepage/enabled) 26 | ./testingmlp 27 | done 28 | echo "Done." 29 | -------------------------------------------------------------------------------- /gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | print("#include \"common.hpp\"\n#include \"math.h\"\n") 4 | 5 | for i in range(1,100): 6 | print("uint64_t naked_access_%d(const uint64_t *sp, const uint64_t *bigarray, size_t howmanyhits) {"%(i)) 7 | for j in range(i): 8 | print(" uint64_t val%d = sp[%d];" %(j,j)) 9 | print (" size_t howmanyhits_perlane = howmanyhits / %d;"%(i)) 10 | print(" for (size_t counter = 0; counter < howmanyhits_perlane; counter++) {") 11 | for j in range(i): 12 | print(" val%d = bigarray[val%d];" %(j,j)) 13 | print(" }") 14 | print(" return ", "+".join("val%d"%j for j in range(i)), ";") 15 | print("}") 16 | 17 | # make the method array 18 | print('access_method_f * all_methods[] = {') 19 | print(',\n'.join("\tnaked_access_%d"%i for i in range(1,100))) 20 | print('\n};') 21 | -------------------------------------------------------------------------------- /common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MLP_COMMON_H_ 2 | #define MLP_COMMON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* max MLP value tested with the naked strategy */ 14 | constexpr int NAKED_MAX = 100; 15 | 16 | void naked_measure_body(float (&time_measure)[NAKED_MAX], uint64_t *bigarray, size_t howmanyhits, size_t repeat); 17 | 18 | typedef uint64_t (access_method_f)(const uint64_t* sp, const uint64_t *bigarray, size_t howmanyhits); 19 | 20 | /** get the method implementing mlp_count access chains, up to NAKED_MAX */ 21 | extern access_method_f * all_methods[]; 22 | 23 | static inline access_method_f * get_method(size_t mlp) { 24 | assert(mlp >= 1 && mlp < NAKED_MAX); 25 | return all_methods[mlp - 1]; 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # testingmlp: Testing memory-level parallelism 2 | 3 | Processor cores can issue multiple memory requests. How many concurrent memory requests can your processor cores support? 4 | 5 | The answer seem to vary between 1 and 25, more or less. 6 | 7 | To assess memory-level parallelism, we designed a pointer-chasing benchmark that relies on multiple "lanes" (independent pointer-chasing routines). By increasing the number of "lanes" up to the point where performance stops increasing, we can measure the level parallelism in your processor. 8 | 9 | Our benchmark is designed for Linux. It won't work on Windows. 10 | 11 | One limitation of our approach has to do with page walking: we work we are RAM-size array and the translation lookaside buffer (TLB) is put to work. If at all possible, you should run this benchmark with transparent huge pages. If you cannot, then report this limitation along with your numbers. 12 | 13 | 14 | 15 | 16 | ## Usage 17 | 18 | ``` 19 | make 20 | ./testingmlp 21 | ``` 22 | 23 | 24 | ## Sample output 25 | 26 | ``` 27 | $ ./testingmlp 28 | Initializing array made of 33554432 64-bit words. 29 | Applying Sattolo's algorithm. 30 | Surgery on the long cycle. 31 | Verifying the neighboring distance... 32 | mindist = 335544 vs 335544 33 | Time to sum up the array (linear scan) 0.011 s (x 8 = 0.084 s), bandwidth = 24256.2 MB/s 34 | Legend: 35 | BandW: Implied bandwidth (assuming 64-byte cache line) in MB/s 36 | % Eff: Effectiness of this lane count compared to the prior, as a % of ideal 37 | Speedup: Speedup factor for this many lanes versus one lane 38 | --------------------------------------------------------------------- 39 | - # of lanes --- time (s) ---- BandW -- ns/hit -- % Eff -- Speedup -- 40 | --------------------------------------------------------------------- 41 | 1 2.719856 753 81.1 0% 1.0 42 | 2 1.366652 1499 40.7 100% 2.0 43 | 3 0.915235 2238 27.3 99% 3.0 44 | 4 0.690618 2965 20.6 98% 3.9 45 | 5 0.556533 3680 16.6 97% 4.9 46 | 6 0.469215 4365 14.0 94% 5.8 47 | 7 0.406813 5034 12.1 93% 6.7 48 | 8 0.359100 5703 10.7 94% 7.6 49 | 9 0.323974 6321 9.7 88% 8.4 50 | 10 0.296101 6917 8.8 86% 9.2 51 | 11 0.278978 7341 8.3 64% 9.7 52 | 12 0.269778 7591 8.0 40% 10.1 53 | ``` 54 | -------------------------------------------------------------------------------- /page-info.h: -------------------------------------------------------------------------------- 1 | /* 2 | * page-info.h 3 | */ 4 | 5 | #ifndef PAGE_INFO_H_ 6 | #define PAGE_INFO_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | typedef struct { 18 | /* page frame number: if present, the physical frame for the page */ 19 | uint64_t pfn; 20 | /* soft-dirty set */ 21 | bool softdirty; 22 | /* exclusively mapped, see e.g., https://patchwork.kernel.org/patch/6787921/ */ 23 | bool exclusive; 24 | /* is a file mapping */ 25 | bool file; 26 | /* page is swapped out */ 27 | bool swapped; 28 | /* page is present, i.e, a physical page is allocated */ 29 | bool present; 30 | /* if true, the kpageflags were successfully loaded, if false they were not (and are all zero) */ 31 | bool kpageflags_ok; 32 | /* the 64-bit flag value extracted from /proc/kpageflags only if pfn is non-null */ 33 | uint64_t kpageflags; 34 | 35 | } page_info; 36 | /* 37 | * Information for a number of virtually consecutive pages. 38 | */ 39 | typedef struct { 40 | /* how many page_info structures are in the array pointed to by info */ 41 | size_t num_pages; 42 | 43 | /* pointer to the array of page_info structures */ 44 | page_info *info; 45 | } page_info_array; 46 | 47 | 48 | typedef struct { 49 | /* the number of pages on which this flag was set, always <= pages_available */ 50 | size_t pages_set; 51 | 52 | /* the number of pages on which information could be obtained */ 53 | size_t pages_available; 54 | 55 | /* the total number of pages examined, which may be greater than pages_available if 56 | * the flag value could not be obtained for some pages (usually because the pfn is not available 57 | * since the page is not yet present or because running as non-root. 58 | */ 59 | size_t pages_total; 60 | 61 | /* the flag the values were queried for */ 62 | int flag; 63 | 64 | } flag_count; 65 | 66 | /** 67 | * Examine the page info in infos to count the number of times a specified /proc/kpageflags flag was set, 68 | * effectively giving you a ratio, so you can say "80% of the pages for this allocation are backed by 69 | * huge pages" or whatever. 70 | * 71 | * The flags *must* come from kpageflags (these are not the same as those in /proc/pid/pagemap) and 72 | * are declared in linux/kernel-page-flags.h. 73 | * 74 | * Ideally, the flag information is available for all the pages in the range, so you can 75 | * say something about the entire range, but this is often not the case because (a) flags 76 | * are not available for pages that aren't present and (b) flags are generally never available 77 | * for non-root users. So the ratio structure indicates both the total number of pages as 78 | * well as the number of pages for which the flag information was available. 79 | */ 80 | flag_count get_flag_count(page_info_array infos, int flag); 81 | 82 | /** 83 | * Given the case-insensitive name of a flag, return the flag number (the index of the bit 84 | * representing this flag), or -1 if the flag is not found. The "names" of the flags are 85 | * the same as the macro names in without the KPF_ prefix. 86 | * 87 | * For example, the name of the transparent hugepages flag is "THP" and the corresponding 88 | * macro is KPF_THP, and the value of this macro and returned by this method is 22. 89 | * 90 | * You can generate the corresponding mask value to check the flag using (1ULL << value). 91 | */ 92 | int flag_from_name(char const *name); 93 | 94 | /** 95 | * Print the info in the page_info structure to stdout. 96 | */ 97 | void print_info(page_info info); 98 | 99 | /** 100 | * Print the info in the page_info structure to the give file. 101 | */ 102 | void fprint_info(FILE* file, page_info info); 103 | 104 | 105 | /** 106 | * Print the table header that lines up with the tabluar format used by the "table" printing 107 | * functions. Called by fprint_ratios, or you can call it yourself if you want to prefix the 108 | * output with your own columns. 109 | */ 110 | void fprint_info_header(FILE *file); 111 | 112 | /* print one info in a tabular format (as a single row) */ 113 | void fprint_info_row(FILE *file, page_info info); 114 | 115 | 116 | /** 117 | * Print the ratio for each flag in infos. The ratio is the number of times the flag was set over 118 | * the total number of pages (or the total number of pages for which the information could be obtained). 119 | */ 120 | void fprint_ratios_noheader(FILE *file, page_info_array infos); 121 | /* 122 | * Print a table with one row per page from the given infos. 123 | */ 124 | void fprint_ratios(FILE *file, page_info_array infos); 125 | 126 | /* 127 | * Prints a summary of all the pages in the given array as ratios: the fraction of the time the given 128 | * flag was set. 129 | */ 130 | void fprint_table(FILE *f, page_info_array infos); 131 | 132 | 133 | /** 134 | * Get info for a single page indicated by the given pointer (which may point anywhere in the page). 135 | */ 136 | page_info get_page_info(void *p); 137 | 138 | /** 139 | * Get information for each page in the range from start (inclusive) to end (exclusive). 140 | */ 141 | page_info_array get_info_for_range(void *start, void *end); 142 | 143 | /** 144 | * Free the memory associated with the given page_info_array. You shouldn't use it after this call. 145 | */ 146 | void free_info_array(page_info_array infos); 147 | 148 | #ifdef __cplusplus 149 | } 150 | #endif 151 | 152 | #endif /* PAGE_INFO_H_ */ 153 | -------------------------------------------------------------------------------- /plot-csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import matplotlib.pyplot as mpl 4 | import matplotlib.pyplot as plt 5 | import matplotlib.ticker as plticker 6 | import pandas as pd 7 | import numpy as np 8 | import csv 9 | import argparse 10 | import sys 11 | import collections 12 | import os 13 | import json 14 | 15 | # for arguments that should be comma-separate lists, we use splitlsit as the type 16 | splitlist = lambda x: x.split(',') 17 | 18 | p = argparse.ArgumentParser(usage='plot output from PLOT=1 ./bench') 19 | 20 | # input and output file configuration 21 | p.add_argument('input', help='CSV file to plot (or stdin)', nargs='*', 22 | type=argparse.FileType('r'), default=[ sys.stdin ]) 23 | p.add_argument('--out', help='output filename') 24 | 25 | # input parsing configuration 26 | p.add_argument('--sep', help='separator character (or regex) for input', default=',') 27 | 28 | # column selection and configuration 29 | p.add_argument('--xcol', help='Column index to use as x axis (default: 0)', type=int, default=0) 30 | p.add_argument('--cols-by-name', help='Use only these comma-separated columns, specified by "name", i.e., the column header (default: all columns)', 31 | type=splitlist) 32 | p.add_argument('--allxticks', help="Force one x-axis tick for each value, disables auto ticks and may crowd x-axis", action='store_true') 33 | p.add_argument('--cols', help='Use only these zero-based columns on primary axis (default: all columns)', 34 | type=int, nargs='+') 35 | p.add_argument('--cols2', help='Use only these zero-based columns on secondary axis (default: no secondary axis)', 36 | type=int, nargs='+') 37 | p.add_argument('--color-map', help='A JSON map from column name to color to use for that column', 38 | type=json.loads) 39 | 40 | # chart labels and text 41 | p.add_argument('--clabels', help="Comma separated list of column names used as label for data series (default: column header)", 42 | type=splitlist) 43 | p.add_argument('--scatter', help='Do an XY scatter plot (default is a line splot with x values used only as labels)', action='store_true') 44 | p.add_argument('--title', help='Set chart title', default='Some chart (use --title to specify title)') 45 | p.add_argument('--xlabel', help='Set x axis label') 46 | p.add_argument('--ylabel', help='Set y axis label') 47 | p.add_argument('--suffix-names', help='Suffix each column name with the file it came from', action='store_true') 48 | p.add_argument('--no-legend', help='Do no display the legend', action='store_true') 49 | 50 | # data manipulation 51 | p.add_argument('--jitter', help='Apply horizontal (x-axis) jitter of the given relative amount (default 0.1)', 52 | nargs='?', type=float, const=0.1) 53 | p.add_argument('--group', help='Group data by the first column, with new min/median/max columns with one row per group') 54 | 55 | # axis and line/point configuration 56 | p.add_argument('--ylim', help='Set the y axis limits explicitly (e.g., to cross at zero)', type=float, nargs='+') 57 | p.add_argument('--xrotate', help='rotate the xlablels by this amount', default=0) 58 | p.add_argument('--tick-interval', help='use the given x-axis tick spacing (in x axis units)', type=int) 59 | p.add_argument('--marker', help='use the given marker', type=str) 60 | p.add_argument('--markersize', help='use the given marker', type=float) 61 | p.add_argument('--linewidth', help='use the given line width', type=float) 62 | p.add_argument('--tight', help='use tight_layout for less space around chart', action='store_true') 63 | p.add_argument('--xscale', help='set the xscale value, e.g., --xscale log for logarithmic x axis', type=str) 64 | p.add_argument('--yscale', help='set the yscale value, e.g., --yscale log for logarithmic y axis', type=str) 65 | 66 | 67 | # debugging 68 | p.add_argument('--verbose', '-v', help='enable verbose logging', action='store_true') 69 | args = p.parse_args() 70 | 71 | vprint = print if args.verbose else lambda *a: None 72 | vprint("args = ", args) 73 | 74 | # fix various random seeds so we get reproducible plots 75 | # fix the mpl seed used to generate SVG IDs 76 | mpl.rcParams['svg.hashsalt'] = 'foobar' 77 | 78 | # numpy random seeds (used by e.g., jitter function below) 79 | np.random.seed(123) 80 | 81 | # if we are reading from stdin and stdin is a tty, print a warning since maybe the user just messed up 82 | # the arguments and otherwise we just appear to hang 83 | if (args.input and args.input[0] == sys.stdin): 84 | print("reading from standard input...", file=sys.stderr) 85 | 86 | xi = args.xcol 87 | dfs = [] 88 | for f in args.input: 89 | df = pd.read_csv(f, sep=args.sep) 90 | if args.suffix_names: 91 | df = df.add_suffix(' ' + os.path.basename(f.name)) 92 | vprint("----- df from: ", f.name, "-----\n", df.head(), "\n---------------------") 93 | dfs.append(df) 94 | 95 | df = pd.concat(dfs, axis=1) 96 | vprint("----- merged df -----\n", df.head(), "\n---------------------") 97 | 98 | # renames duplicate columns by suffixing _1, _2 etc 99 | class renamer(): 100 | def __init__(self): 101 | self.d = dict() 102 | 103 | def __call__(self, x): 104 | if x not in self.d: 105 | self.d[x] = 0 106 | return x 107 | else: 108 | self.d[x] += 1 109 | return "%s_%d" % (x, self.d[x]) 110 | 111 | 112 | # rename any duplicate columns because otherwise Pandas gets mad 113 | df = df.rename(columns=renamer()) 114 | 115 | vprint("---- renamed df ----\n", df.head(), "\n---------------------") 116 | 117 | def col_names_to_indices(requested, df): 118 | vprint("requested columns: ", requested) 119 | colnames = [x.strip() for x in df.columns.tolist()] 120 | vprint("actual columns: ", colnames) 121 | cols = [] 122 | for name in requested: 123 | if not name in colnames: 124 | exit("column name " + name + " not found, input columns were: " + ','.join(colnames)) 125 | cols.append(colnames.index(name)) 126 | return cols 127 | 128 | 129 | def extract_cols(cols, df, name): 130 | vprint(name, "axis columns: ", cols) 131 | if (not cols): return None 132 | if (max(cols) >= len(df.columns)): 133 | print("Column", max(cols), "too large: input only has", len(df.columns), "columns", file=sys.stderr) 134 | exit(1) 135 | # ensure xi is the first thing in the column list 136 | if xi in cols: cols.remove(xi) 137 | cols = [xi] + cols 138 | vprint(name, " final columns: ", cols) 139 | pruned = df.iloc[:, cols] 140 | vprint("----- pruned ", name, " df -----\n", pruned.head(), "\n---------------------") 141 | return pruned 142 | 143 | 144 | if args.cols_by_name: 145 | cols = col_names_to_indices(args.cols_by_name, df) 146 | elif args.cols: 147 | cols = args.cols 148 | else: 149 | cols = list(range(len(df.columns))) 150 | 151 | df = extract_cols(cols, df, "primary") 152 | df2 = extract_cols(args.cols2, df, "secondary") 153 | 154 | if args.clabels: 155 | if len(df.columns) != len(args.clabels): 156 | sys.exit("ERROR: number of column labels " + str(len(args.clabels)) + 157 | " not equal to the number of selected columns " + str(len(df.columns))) 158 | df.columns = args.clabels 159 | 160 | # dupes will break pandas beyond this point, should be impossible due to above renaming 161 | dupes = df.columns.duplicated() 162 | if True in dupes: 163 | print("Duplicate columns after merge and pruning, consider --suffix-names", 164 | df.columns[dupes].values.tolist(), file=sys.stderr) 165 | exit(1) 166 | 167 | # do grouping (feature not complete) 168 | if (args.group): 169 | vprint("before grouping\n", df) 170 | 171 | dfg = df.groupby(by=df.columns[0]) 172 | 173 | df = dfg.agg([min, pd.DataFrame.median, max]) 174 | 175 | vprint("agg\n---------------\n", df) 176 | 177 | df.columns = [tup[0] + ' (' + tup[1] + ')' for tup in df.columns.values] 178 | df.reset_index(inplace=True) 179 | 180 | vprint("flat\n---------------\n", df) 181 | 182 | def jitter(arr, multiplier): 183 | stdev = multiplier*(max(arr)-min(arr))/len(arr) 184 | return arr if not len(arr) else arr + np.random.randn(len(arr)) * stdev 185 | 186 | if args.jitter: 187 | df.iloc[:,xi] = jitter(df.iloc[:,xi], args.jitter) 188 | 189 | kwargs = {} 190 | 191 | if (args.linewidth): 192 | kwargs["linewidth"] = args.linewidth 193 | 194 | if args.color_map: 195 | colors = [] 196 | for i, cname in enumerate(df.columns): 197 | if i == xi: 198 | continue 199 | if cname in args.color_map: 200 | vprint("Using color {} for column {}".format(args.color_map[cname], cname)) 201 | colors.append(args.color_map[cname]) 202 | else: 203 | print("WARNING no entry for column {} in given color-map".format(cname)) 204 | vprint("colors = ", colors) 205 | kwargs["color"] = colors 206 | 207 | if (args.scatter): 208 | kwargs['linestyle'] = 'none' 209 | kwargs['marker'] = args.marker if args.marker else '.' 210 | elif (args.marker): 211 | kwargs['marker'] = args.marker 212 | 213 | if (args.markersize): 214 | kwargs['markersize'] = args.markersize 215 | 216 | # set x labels to strings so we don't get a scatter plot, and 217 | # so the x labels are not themselves plotted 218 | #if (args.scatter): 219 | # ax = df.plot.line(x=0, title=args.title, figsize=(12,8), grid=True, **kwargs) 220 | #else: 221 | # df.iloc[:,xi] = df.iloc[:,xi].apply(str) 222 | ax = df.plot.line(x=0, title=args.title, figsize=(12,8), grid=True, **kwargs) 223 | 224 | # this sets the ticks explicitly to one per x value, which means that 225 | # all x values will be shown, but the x-axis could be crowded if there 226 | # are too many 227 | if args.allxticks: 228 | ticks = df.iloc[:,xi].values 229 | plt.xticks(ticks=range(len(ticks)), labels=ticks) 230 | 231 | if (args.tick_interval): 232 | ax.xaxis.set_major_locator(plticker.MultipleLocator(base=args.tick_interval)) 233 | 234 | if (args.xrotate): 235 | plt.xticks(rotation=args.xrotate) 236 | 237 | if args.ylabel: 238 | ax.set_ylabel(args.ylabel) 239 | 240 | if args.xlabel: 241 | ax.set_xlabel(args.xlabel) 242 | 243 | if (args.xscale): ax.set_xscale(args.xscale) 244 | if (args.yscale): ax.set_yscale(args.yscale) 245 | 246 | if (args.no_legend): ax.get_legend().remove() 247 | 248 | if args.ylim: 249 | if (len(args.ylim) == 1): 250 | ax.set_ylim(args.ylim[0]) 251 | elif (len(args.ylim) == 2): 252 | ax.set_ylim(args.ylim[0], args.ylim[1]) 253 | else: 254 | sys.exit('provide one or two args to --ylim') 255 | 256 | # secondary axis handling 257 | if df2 is not None: 258 | df2.plot(x=0, secondary_y=True, ax=ax, grid=True) 259 | 260 | if (args.tight): 261 | plt.tight_layout() 262 | 263 | if (args.out): 264 | vprint("Saving figure to ", args.out, "...") 265 | plt.savefig(args.out) 266 | else: 267 | vprint("Showing interactive plot...") 268 | plt.show() 269 | -------------------------------------------------------------------------------- /testingmlp.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | #ifdef __linux__ 3 | #include "page-info.h" 4 | #endif 5 | #include 6 | #include 7 | #ifdef __linux__ 8 | #include 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int getenv_int(const char *var, int def) { 16 | const char *val = getenv(var); 17 | return val ? atoi(val) : def; 18 | } 19 | 20 | bool getenv_bool(const char *var) { 21 | const char *val = getenv(var); 22 | return val && strcmp(val, "1") == 0; 23 | } 24 | 25 | const int do_csv = getenv_int("MLP_CSV", 0); 26 | const int do_distr = getenv_int("MLP_DISTRIBUTION", 0); 27 | const size_t len_start = getenv_int("MLP_START", 32 * 1024) * 1024ull; // 1 KiB 28 | const size_t len_end = getenv_int("MLP_STOP", 32 * 1024) * 1024ull; // 256 MiB 29 | 30 | 31 | FILE* ifile = do_csv ? stderr : stdout; 32 | 33 | #define printi(...) fprintf(ifile, __VA_ARGS__) 34 | 35 | template 36 | std::tuple compute_min_mean_std_max(const std::vector& input) { 37 | 38 | T m = 0; 39 | T ma = input[0]; 40 | T mi = input[0]; 41 | 42 | for(T v : input) { 43 | if(ma < v) { ma = v; } 44 | if(mi > v) { mi = v; } 45 | m += v; 46 | } 47 | m /= input.size(); 48 | 49 | T std = 0; 50 | for(T v : input) { 51 | std += (v - m) * (v - m) / input.size(); 52 | } 53 | std = sqrt(std); 54 | return {mi, m, std, ma}; 55 | } 56 | 57 | float time_one(const uint64_t* sp, 58 | const uint64_t *bigarray, 59 | size_t howmanyhits, 60 | size_t repeat, 61 | access_method_f *method, 62 | size_t lanes, 63 | float firsttime, 64 | float lasttime) { 65 | using namespace std::chrono; 66 | double mintime = 99999999999; 67 | uint64_t bogus = 0; 68 | std::vector timings(repeat); 69 | for (size_t r = 0; r < repeat; r++) { 70 | auto begin_time = high_resolution_clock::now(); 71 | bogus += method(sp, bigarray, howmanyhits); 72 | auto end_time = high_resolution_clock::now(); 73 | double tv = duration(end_time - begin_time).count(); 74 | timings[r] = tv; 75 | if (tv < mintime) { 76 | mintime = tv; 77 | } 78 | } 79 | auto [min_timing, mean_timing, std_timing, max_timing] = compute_min_mean_std_max(timings); 80 | if (bogus == 0x010101) { 81 | printf("ping!"); 82 | } 83 | // compute the bandwidth 84 | size_t cachelineinbytes = 64; 85 | size_t rounded_hits = ( howmanyhits / lanes * lanes ); 86 | size_t volume = rounded_hits * cachelineinbytes; 87 | double mbpers = volume / mintime / (1024.0 * 1024.); 88 | double nanoperquery = 1000 * 1000 * 1000 * mintime / rounded_hits; 89 | double expected = lasttime * (lanes - 1) / lanes; // expected time if at max efficiency 90 | double efficiency = lanes == 1 ? 0 : 100.0 * (lasttime - mintime) / (lasttime - expected); 91 | double speedup = lanes == 1 ? 1 : firsttime / mintime; 92 | if (do_csv) { 93 | switch (do_csv) { 94 | case 1: printf(",%.1f", mbpers); break; 95 | case 2: printf(",%.3f", speedup); break; 96 | default: assert(false); 97 | } 98 | } else { 99 | printf("%12zu %12f %10.0f %8.1f %6.0f%% %9.1f\n", 100 | lanes, mintime, round(mbpers), nanoperquery, efficiency, speedup); 101 | } 102 | if(do_distr) { 103 | double top_sigma = (max_timing - mean_timing) / std_timing; 104 | double bottom_sigma = (mean_timing - min_timing) / std_timing; 105 | printf("timing distribution: N: %zu, min: %.3f, mean: %.3f, std: %f, max : %.3f, bottom sigma: %.1f, top sigma: %.1f \n", 106 | repeat, min_timing, mean_timing, std_timing, max_timing, bottom_sigma, top_sigma); 107 | } 108 | 109 | return mintime; 110 | } 111 | 112 | /* advance starting at p, n times */ 113 | size_t incr(const uint64_t* array, uint64_t p, size_t n) { 114 | while (n--) { 115 | p = array[p]; 116 | } 117 | return p; 118 | } 119 | 120 | size_t cycle_dist(const uint64_t* array, uint64_t from, uint64_t to) { 121 | size_t dist = 0; 122 | while (from != to) { 123 | from = array[from]; 124 | dist++; 125 | } 126 | return dist; 127 | } 128 | 129 | size_t cycle_total(const uint64_t* array) { 130 | auto first = array[0]; 131 | return 1 + cycle_dist(array, first, 0); 132 | } 133 | 134 | /** make a cycle of length starting at element 0 of the given array */ 135 | void make_cycle(uint64_t* array, uint64_t* index, size_t length) { 136 | // create a cycle of maximum length within the bigarray 137 | for (size_t i = 0; i < length; i++) { 138 | array[i] = i; 139 | } 140 | 141 | if (!do_csv) { 142 | printi("Applying Sattolo's algorithm... "); 143 | fflush(ifile); 144 | } 145 | // Sattolo 146 | std::mt19937_64 engine; 147 | engine.seed(0xBABE); 148 | for (size_t i = 0; i + 1 < length; i++) { 149 | std::uniform_int_distribution dist{i + 1, length - 1}; 150 | size_t swapidx = dist(engine); 151 | std::swap(array[i], array[swapidx]); 152 | } 153 | 154 | size_t total = 0; 155 | uint64_t cur = 0; 156 | do { 157 | index[total++] = cur; 158 | cur = array[cur]; 159 | assert(cur < length); 160 | } while (cur != 0); 161 | 162 | if (!do_csv) printi("chain total: %zu\n", cycle_total(array)); 163 | assert(total == length); 164 | } 165 | 166 | void setup_pointers(uint64_t* sp, const uint64_t* array, const uint64_t* index, size_t length, size_t mlp) { 167 | std::fill(sp, sp + NAKED_MAX, -1); 168 | sp[0] = 0; 169 | size_t totalinc = 0; 170 | for (size_t m = 1; m < mlp; m++) { 171 | totalinc += length / mlp; 172 | assert(totalinc < length); 173 | sp[m] = index[totalinc]; 174 | } 175 | 176 | if (!do_csv && mlp > 1) { 177 | size_t mind = -1, maxd = 0; 178 | for (size_t i = 0; i < mlp; i++) { 179 | bool last = (i + 1 == mlp); 180 | uint64_t from = sp[i]; 181 | uint64_t to = sp[last ? 0 : i + 1]; 182 | size_t dist = cycle_dist(array, from, to); 183 | mind = std::min(mind, dist); 184 | maxd = std::max(maxd, dist); 185 | } 186 | assert(mind >= length / mlp); // check that the min distance is as good as expected 187 | } 188 | } 189 | 190 | int naked_measure(uint64_t* bigarray, uint64_t* index, size_t length, size_t max_mlp) { 191 | 192 | make_cycle(bigarray, index, length); 193 | 194 | if (!do_csv) { 195 | uint64_t sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0; 196 | clock_t begin_time, end_time; 197 | int sumrepeat = 10; 198 | float mintime = 99999999999; 199 | while (sumrepeat-- >0) { 200 | begin_time = clock(); 201 | for(size_t i = 0; i < length - 3 * 8; i+= 32) { 202 | sum1 ^= bigarray[i]; 203 | sum2 ^= bigarray[i + 8]; 204 | sum3 ^= bigarray[i + 16]; 205 | sum4 ^= bigarray[i + 24]; 206 | } 207 | end_time = clock(); 208 | float tv = float(end_time - begin_time) / CLOCKS_PER_SEC; 209 | if (tv < mintime) 210 | mintime = tv; 211 | } 212 | if((sum1 ^ sum2 ^ sum3 ^ sum4) == 0x1010) printf("bug"); 213 | printi("Time to sum up the array (linear scan) %.3f s (x 8 = %.3f s), bandwidth = %.1f MB/s \n", 214 | mintime, 8*mintime, length * sizeof(uint64_t) / mintime / (1024.0 * 1024.0)); 215 | } 216 | 217 | float time_measure[NAKED_MAX] = {}; 218 | size_t howmanyhits = length; 219 | int repeat = 30; 220 | if (do_csv) { 221 | printi("Running test for length %zu\n", length); 222 | printf("%zu", length); 223 | } else { 224 | printi("Size: %zu (%5.2f KiB, %5.2f MiB)\n", length, length * 8. / 1024., length * 8. / 1024. / 1024.); 225 | printi("---------------------------------------------------------------------\n"); 226 | printi("- # of lanes --- time (s) ---- BandW -- ns/hit -- %% Eff -- Speedup --\n"); 227 | printi("---------------------------------------------------------------------\n"); 228 | } 229 | 230 | uint64_t starting_pointers[NAKED_MAX]; 231 | 232 | // naked_measure_body(time_measure, bigarray, howmanyhits, repeat); 233 | for (size_t m = 1; m <= max_mlp; m++) { 234 | setup_pointers(starting_pointers, bigarray, index, length, m); 235 | time_measure[m] = time_one(starting_pointers, bigarray, howmanyhits, repeat, get_method(m), m, time_measure[1], time_measure[m - 1]); 236 | } 237 | 238 | if (do_csv) { 239 | printf("\n"); 240 | } else { 241 | for (size_t i = 2; i < NAKED_MAX; i++) { 242 | float ratio = (time_measure[i - 1] - time_measure[i]) / time_measure[i - 1]; 243 | 244 | if (ratio < 0.01) // if a new lane does not add at least 1% of performance... 245 | { 246 | std::cout << "Maybe you have about " << i - 1 << " parallel paths? " 247 | << std::endl; 248 | return i - 1; 249 | break; 250 | } 251 | } 252 | printi("--------------------------------------------------------------\n"); 253 | } 254 | 255 | return 10000; 256 | } 257 | 258 | #ifdef __linux__ 259 | void print_page_info(uint64_t *array, size_t length) { 260 | constexpr int KPF_THP = 22; 261 | page_info_array pinfo = get_info_for_range(array, array + length); 262 | flag_count thp_count = get_flag_count(pinfo, KPF_THP); 263 | if (thp_count.pages_available) { 264 | printi("Source pages allocated with transparent hugepages: %4.1f%% (%lu pages, %4.1f%% flagged)\n", 265 | 100.0 * thp_count.pages_set / thp_count.pages_total, thp_count.pages_total, 266 | 100.0 * thp_count.pages_available / thp_count.pages_total); 267 | } else { 268 | printi("Couldn't determine hugepage info (you are probably not running as root)\n"); 269 | } 270 | } 271 | #endif 272 | 273 | void *malloc_aligned(size_t size, size_t alignment) { 274 | #ifdef __linux__ 275 | size = ((size - 1) / alignment + 1) * alignment; 276 | return memalign(alignment, size); 277 | #else 278 | return malloc(size); // ignores alignment? 279 | #endif 280 | } 281 | 282 | int main() { 283 | assert(do_csv >= 0 && do_csv <= 2); 284 | size_t max_mlp = getenv_int("MLP_MAX_MLP", 40); 285 | printi("Initializing array made of %zu 64-bit words (%5.2f MiB).\n", len_end, len_end * 8. / 1024. / 1024.); 286 | uint64_t *array = (uint64_t *)malloc_aligned(sizeof(uint64_t) * len_end, 2 * 1024 * 1024); 287 | #ifdef __linux__ 288 | madvise(array, len_end * 8, MADV_HUGEPAGE); 289 | #endif 290 | std::fill(array, array + len_end, -1); 291 | uint64_t *index = (uint64_t *)malloc_aligned(sizeof(uint64_t) * len_end, 2 * 1024 * 1024); 292 | #ifdef __linux__ 293 | print_page_info(array, len_end); 294 | #endif 295 | 296 | if (!do_csv) { 297 | printi("Legend:\n" 298 | " BandW: Implied bandwidth (assuming 64-byte cache line) in MB/s\n" 299 | " %% Eff: Effectiness of this lane count compared to the prior, as a %% of ideal\n" 300 | " Speedup: Speedup factor for this many lanes versus one lane\n" 301 | ); 302 | } else { 303 | printf("size,"); 304 | for (size_t m = 1; m <= max_mlp; m++) { 305 | printf("%zu%s", m, m == max_mlp ? "\n" : ","); 306 | } 307 | } 308 | 309 | // for (size_t length = len_start; length <= len_end; length *= 2) { 310 | for (int i = 0; ; i++) { 311 | size_t length = round(len_start * pow(2., i / 4.)); 312 | if (length > len_end) 313 | break; 314 | naked_measure(array, index, length, max_mlp); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /page-info.c: -------------------------------------------------------------------------------- 1 | /* 2 | * smaps.c 3 | * 4 | * Created on: Jan 31, 2017 5 | * Author: tdowns 6 | */ 7 | 8 | #ifdef __linux__ 9 | #include "page-info.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | #define PM_PFRAME_MASK ((1ULL << 55) - 1) 26 | #define PM_SOFT_DIRTY (1ULL << 55) 27 | #define PM_MMAP_EXCLUSIVE (1ULL << 56) 28 | #define PM_FILE (1ULL << 61) 29 | #define PM_SWAP (1ULL << 62) 30 | #define PM_PRESENT (1ULL << 63) 31 | 32 | 33 | /** bundles a flag with its description */ 34 | typedef struct { 35 | int flag_num; 36 | char const *name; 37 | bool show_default; 38 | } flag; 39 | 40 | #define FLAG_SHOW(name) { KPF_ ## name, # name, true }, 41 | #define FLAG_HIDE(name) { KPF_ ## name, # name, false }, 42 | 43 | const flag kpageflag_defs[] = { 44 | FLAG_SHOW(LOCKED ) 45 | FLAG_HIDE(ERROR ) 46 | FLAG_HIDE(REFERENCED ) 47 | FLAG_HIDE(UPTODATE ) 48 | FLAG_HIDE(DIRTY ) 49 | FLAG_HIDE(LRU ) 50 | FLAG_SHOW(ACTIVE ) 51 | FLAG_SHOW(SLAB ) 52 | FLAG_HIDE(WRITEBACK ) 53 | FLAG_HIDE(RECLAIM ) 54 | FLAG_SHOW(BUDDY ) 55 | FLAG_SHOW(MMAP ) 56 | FLAG_SHOW(ANON ) 57 | FLAG_SHOW(SWAPCACHE ) 58 | FLAG_SHOW(SWAPBACKED ) 59 | FLAG_SHOW(COMPOUND_HEAD) 60 | FLAG_SHOW(COMPOUND_TAIL) 61 | FLAG_SHOW(HUGE ) 62 | FLAG_SHOW(UNEVICTABLE ) 63 | FLAG_SHOW(HWPOISON ) 64 | FLAG_SHOW(NOPAGE ) 65 | FLAG_SHOW(KSM ) 66 | FLAG_SHOW(THP ) 67 | /* older kernels won't have these new flags, so conditionally compile in support for them */ 68 | #ifdef KPF_BALLOON 69 | FLAG_SHOW(BALLOON ) 70 | #endif 71 | #ifdef KPF_ZERO_PAGE 72 | FLAG_SHOW(ZERO_PAGE ) 73 | #endif 74 | #ifdef KPF_IDLE 75 | FLAG_SHOW(IDLE ) 76 | #endif 77 | 78 | { -1, 0, false } // sentinel 79 | }; 80 | 81 | #define kpageflag_count (sizeof(kpageflag_defs)/sizeof(kpageflag_defs[0]) - 1) 82 | 83 | #define ITERATE_FLAGS for (flag const *f = kpageflag_defs; f->flag_num != -1; f++) 84 | 85 | 86 | // x-macro for doing some operation on all the pagemap flags 87 | #define PAGEMAP_X(fn) \ 88 | fn(softdirty ) \ 89 | fn(exclusive ) \ 90 | fn(file ) \ 91 | fn(swapped ) \ 92 | fn(present ) 93 | 94 | static unsigned get_page_size() { 95 | long psize = sysconf(_SC_PAGESIZE); 96 | assert(psize >= 1 && psize <= UINT_MAX); 97 | return (unsigned)psize; 98 | } 99 | 100 | /* round the given pointer down to the page boundary (i.e,. return a pointer to the page it lives in) */ 101 | static inline void *pagedown(void *p, unsigned psize) { 102 | return (void *)(((uintptr_t)p) & -(uintptr_t)psize); 103 | } 104 | 105 | /** 106 | * Extract the interesting info from a 64-bit pagemap value, and return it as a page_info. 107 | */ 108 | page_info extract_info(uint64_t bits) { 109 | page_info ret = {}; 110 | ret.pfn = bits & PM_PFRAME_MASK; 111 | ret.softdirty = bits & PM_SOFT_DIRTY; 112 | ret.exclusive = bits & PM_MMAP_EXCLUSIVE; 113 | ret.file = bits & PM_FILE; 114 | ret.swapped = bits & PM_SWAP; 115 | ret.present = bits & PM_PRESENT; 116 | return ret; 117 | } 118 | 119 | /* print page_info to the given file */ 120 | void fprint_info(FILE* f, page_info info) { 121 | fprintf(f, 122 | "PFN: %p\n" 123 | "softdirty = %d\n" 124 | "exclusive = %d\n" 125 | "file = %d\n" 126 | "swapped = %d\n" 127 | "present = %d\n", 128 | (void*)info.pfn, 129 | info.softdirty, 130 | info.exclusive, 131 | info.file, 132 | info.swapped, 133 | info.present); 134 | } 135 | 136 | void print_info(page_info info) { 137 | fprint_info(stdout, info); 138 | } 139 | 140 | flag_count get_flag_count(page_info_array infos, int flag_num) { 141 | flag_count ret = {}; 142 | 143 | if (flag_num < 0 || flag_num > 63) { 144 | return ret; 145 | } 146 | 147 | uint64_t flag = (1ULL << flag_num); 148 | 149 | ret.flag = flag_num; 150 | ret.pages_total = infos.num_pages; 151 | 152 | for (size_t i = 0; i < infos.num_pages; i++) { 153 | page_info info = infos.info[i]; 154 | if (info.kpageflags_ok) { 155 | ret.pages_set += (info.kpageflags & flag) == flag; 156 | ret.pages_available++; 157 | } 158 | } 159 | return ret; 160 | } 161 | 162 | /** 163 | * Print the table header that lines up with the tabluar format used by the "table" printing 164 | * functions. Called by fprint_ratios, or you can call it yourself if you want to prefix the 165 | * output with your own columns. 166 | */ 167 | void fprint_info_header(FILE *file) { 168 | fprintf(file, " PFN sdirty excl file swappd presnt "); 169 | ITERATE_FLAGS { if (f->show_default) fprintf(file, "%4.4s ", f->name); } 170 | fprintf(file, "\n"); 171 | } 172 | 173 | /* print one info in a tabular format (as a single row) */ 174 | void fprint_info_row(FILE *file, page_info info) { 175 | fprintf(file, "%12p %7d%7d%7d%7d%7d ", 176 | (void*)info.pfn, 177 | info.softdirty, 178 | info.exclusive, 179 | info.file, 180 | info.swapped, 181 | info.present); 182 | 183 | if (info.kpageflags_ok) { 184 | ITERATE_FLAGS { if (f->show_default) fprintf(file, "%4d ", !!(info.kpageflags & (1ULL << f->flag_num))); } 185 | } 186 | fprintf(file, "\n"); 187 | } 188 | 189 | #define DECLARE_ACCUM(name) size_t name ## _accum = 0; 190 | #define INCR_ACCUM(name) name ## _accum += info->name; 191 | #define PRINT_ACCUM(name) fprintf(file, "%7.4f", (double)name ## _accum / infos.num_pages); 192 | 193 | 194 | void fprint_ratios_noheader(FILE *file, page_info_array infos) { 195 | PAGEMAP_X(DECLARE_ACCUM); 196 | size_t total_kpage_ok = 0; 197 | size_t flag_totals[kpageflag_count] = {}; 198 | for (size_t p = 0; p < infos.num_pages; p++) { 199 | page_info *info = &infos.info[p]; 200 | PAGEMAP_X(INCR_ACCUM); 201 | if (info->kpageflags_ok) { 202 | total_kpage_ok++; 203 | int i = 0; 204 | ITERATE_FLAGS { 205 | flag_totals[i++] += !!(info->kpageflags & (1ULL << f->flag_num)); 206 | } 207 | } 208 | } 209 | 210 | printf("%12s ", "----------"); 211 | PAGEMAP_X(PRINT_ACCUM) 212 | 213 | int i = 0; 214 | if (total_kpage_ok > 0) { 215 | ITERATE_FLAGS { 216 | if (f->show_default) fprintf(file, " %4.2f", (double)flag_totals[i] / total_kpage_ok); 217 | i++; 218 | } 219 | } 220 | fprintf(file, "\n"); 221 | } 222 | 223 | /* 224 | * Print a table with one row per page from the given infos. 225 | */ 226 | void fprint_ratios(FILE *file, page_info_array infos) { 227 | fprint_info_header(file); 228 | fprint_ratios_noheader(file, infos); 229 | } 230 | 231 | /* 232 | * Prints a summary of all the pages in the given array as ratios: the fraction of the time the given 233 | * flag was set. 234 | */ 235 | void fprint_table(FILE *f, page_info_array infos) { 236 | fprintf(f, "%zu total pages\n", infos.num_pages); 237 | fprint_info_header(f); 238 | for (size_t p = 0; p < infos.num_pages; p++) { 239 | fprint_info_row(f, infos.info[p]); 240 | } 241 | } 242 | 243 | 244 | 245 | /** 246 | * Get info for a single page indicated by the given pointer (which may point anywhere in the page) 247 | */ 248 | page_info get_page_info(void *p) { 249 | // just get the info array for a single page 250 | page_info_array onepage = get_info_for_range(p, (char *)p + 1); 251 | assert(onepage.num_pages == 1); 252 | page_info ret = onepage.info[0]; 253 | free_info_array(onepage); 254 | return ret; 255 | } 256 | 257 | /** 258 | * Get information for each page in the range from start (inclusive) to end (exclusive). 259 | */ 260 | page_info_array get_info_for_range(void *start, void *end) { 261 | unsigned psize = get_page_size(); 262 | void *start_page = pagedown(start, psize); 263 | void *end_page = pagedown(end - 1, psize) + psize; 264 | size_t page_count = start < end ? (end_page - start_page) / psize : 0; 265 | assert(page_count == 0 || start_page < end_page); 266 | 267 | if (page_count == 0) { 268 | return (page_info_array){ 0, NULL }; 269 | } 270 | 271 | page_info *infos = malloc((page_count + 1) * sizeof(page_info)); 272 | 273 | // open the pagemap file 274 | FILE *pagemap_file = fopen("/proc/self/pagemap", "rb"); 275 | if (!pagemap_file) err(EXIT_FAILURE, "failed to open pagemap"); 276 | 277 | // seek to the first page 278 | if (fseek(pagemap_file, (uintptr_t)start_page / psize * sizeof(uint64_t), SEEK_SET)) err(EXIT_FAILURE, "pagemap seek failed"); 279 | 280 | size_t bitmap_bytes = page_count * sizeof(uint64_t); 281 | uint64_t* bitmap = malloc(bitmap_bytes); 282 | assert(bitmap); 283 | size_t readc = fread(bitmap, bitmap_bytes, 1, pagemap_file); 284 | if (readc != 1) err(EXIT_FAILURE, "unexpected fread(pagemap) return: %zu", readc); 285 | 286 | fclose(pagemap_file); 287 | 288 | FILE *kpageflags_file = NULL; 289 | enum { INIT, OPEN, FAILED } file_state = INIT; 290 | 291 | for (size_t page_idx = 0; page_idx < page_count; page_idx++) { 292 | page_info info = extract_info(bitmap[page_idx]); 293 | 294 | if (info.pfn) { 295 | // we got a pfn, try to read /proc/kpageflags 296 | 297 | // open file if not open 298 | if (file_state == INIT) { 299 | kpageflags_file = fopen("/proc/kpageflags", "rb"); 300 | if (!kpageflags_file) { 301 | warn("failed to open kpageflags"); 302 | file_state = FAILED; 303 | } else { 304 | file_state = OPEN; 305 | } 306 | } 307 | 308 | if (file_state == OPEN) { 309 | uint64_t bits; 310 | if (fseek(kpageflags_file, info.pfn * sizeof(bits), SEEK_SET)) err(EXIT_FAILURE, "kpageflags seek failed"); 311 | if ((readc = fread(&bits, sizeof(bits), 1, kpageflags_file)) != 1) err(EXIT_FAILURE, "unexpected fread(kpageflags) return: %zu", readc); 312 | info.kpageflags_ok = true; 313 | info.kpageflags = bits; 314 | } 315 | } 316 | 317 | infos[page_idx] = info; 318 | } 319 | 320 | if (kpageflags_file) 321 | fclose(kpageflags_file); 322 | 323 | free(bitmap); 324 | 325 | return (page_info_array){ page_count, infos }; 326 | } 327 | 328 | void free_info_array(page_info_array infos) { 329 | free(infos.info); 330 | } 331 | 332 | int flag_from_name(char const *name) { 333 | ITERATE_FLAGS { 334 | if (strcasecmp(f->name, name) == 0) { 335 | return f->flag_num; 336 | } 337 | } 338 | return -1; 339 | } 340 | 341 | 342 | #endif --------------------------------------------------------------------------------