batch.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /* SPDX-License-Identifier: MIT */
  2. /*
  3. * Description: UBLK_F_BATCH_IO buffer management
  4. */
  5. #include "kublk.h"
  6. static inline void *ublk_get_commit_buf(struct ublk_thread *t,
  7. unsigned short buf_idx)
  8. {
  9. unsigned idx;
  10. if (buf_idx < t->commit_buf_start ||
  11. buf_idx >= t->commit_buf_start + t->nr_commit_buf)
  12. return NULL;
  13. idx = buf_idx - t->commit_buf_start;
  14. return t->commit_buf + idx * t->commit_buf_size;
  15. }
  16. /*
  17. * Allocate one buffer for UBLK_U_IO_PREP_IO_CMDS or UBLK_U_IO_COMMIT_IO_CMDS
  18. *
  19. * Buffer index is returned.
  20. */
  21. static inline unsigned short ublk_alloc_commit_buf(struct ublk_thread *t)
  22. {
  23. int idx = allocator_get(&t->commit_buf_alloc);
  24. if (idx >= 0)
  25. return idx + t->commit_buf_start;
  26. return UBLKS_T_COMMIT_BUF_INV_IDX;
  27. }
  28. /*
  29. * Free one commit buffer which is used by UBLK_U_IO_PREP_IO_CMDS or
  30. * UBLK_U_IO_COMMIT_IO_CMDS
  31. */
  32. static inline void ublk_free_commit_buf(struct ublk_thread *t,
  33. unsigned short i)
  34. {
  35. unsigned short idx = i - t->commit_buf_start;
  36. ublk_assert(idx < t->nr_commit_buf);
  37. ublk_assert(allocator_get_val(&t->commit_buf_alloc, idx) != 0);
  38. allocator_put(&t->commit_buf_alloc, idx);
  39. }
  40. static unsigned char ublk_commit_elem_buf_size(struct ublk_dev *dev)
  41. {
  42. if (dev->dev_info.flags & (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_USER_COPY |
  43. UBLK_F_AUTO_BUF_REG))
  44. return 8;
  45. /* one extra 8bytes for carrying buffer address */
  46. return 16;
  47. }
  48. static unsigned ublk_commit_buf_size(struct ublk_thread *t)
  49. {
  50. struct ublk_dev *dev = t->dev;
  51. unsigned elem_size = ublk_commit_elem_buf_size(dev);
  52. unsigned int total = elem_size * dev->dev_info.queue_depth;
  53. unsigned int page_sz = getpagesize();
  54. return round_up(total, page_sz);
  55. }
  56. static void free_batch_commit_buf(struct ublk_thread *t)
  57. {
  58. if (t->commit_buf) {
  59. unsigned buf_size = ublk_commit_buf_size(t);
  60. unsigned int total = buf_size * t->nr_commit_buf;
  61. munlock(t->commit_buf, total);
  62. free(t->commit_buf);
  63. }
  64. allocator_deinit(&t->commit_buf_alloc);
  65. free(t->commit);
  66. }
  67. static int alloc_batch_commit_buf(struct ublk_thread *t)
  68. {
  69. unsigned buf_size = ublk_commit_buf_size(t);
  70. unsigned int total = buf_size * t->nr_commit_buf;
  71. unsigned int page_sz = getpagesize();
  72. void *buf = NULL;
  73. int i, ret, j = 0;
  74. t->commit = calloc(t->nr_queues, sizeof(*t->commit));
  75. for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) {
  76. if (t->q_map[i])
  77. t->commit[j++].q_id = i;
  78. }
  79. allocator_init(&t->commit_buf_alloc, t->nr_commit_buf);
  80. t->commit_buf = NULL;
  81. ret = posix_memalign(&buf, page_sz, total);
  82. if (ret || !buf)
  83. goto fail;
  84. t->commit_buf = buf;
  85. /* lock commit buffer pages for fast access */
  86. if (mlock(t->commit_buf, total))
  87. ublk_err("%s: can't lock commit buffer %s\n", __func__,
  88. strerror(errno));
  89. return 0;
  90. fail:
  91. free_batch_commit_buf(t);
  92. return ret;
  93. }
  94. static unsigned int ublk_thread_nr_queues(const struct ublk_thread *t)
  95. {
  96. int i;
  97. int ret = 0;
  98. for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++)
  99. ret += !!t->q_map[i];
  100. return ret;
  101. }
  102. void ublk_batch_prepare(struct ublk_thread *t)
  103. {
  104. /*
  105. * We only handle single device in this thread context.
  106. *
  107. * All queues have same feature flags, so use queue 0's for
  108. * calculate uring_cmd flags.
  109. *
  110. * This way looks not elegant, but it works so far.
  111. */
  112. struct ublk_queue *q = &t->dev->q[0];
  113. /* cache nr_queues because we don't support dynamic load-balance yet */
  114. t->nr_queues = ublk_thread_nr_queues(t);
  115. t->commit_buf_elem_size = ublk_commit_elem_buf_size(t->dev);
  116. t->commit_buf_size = ublk_commit_buf_size(t);
  117. t->commit_buf_start = t->nr_bufs;
  118. t->nr_commit_buf = 2 * t->nr_queues;
  119. t->nr_bufs += t->nr_commit_buf;
  120. t->cmd_flags = 0;
  121. if (ublk_queue_use_auto_zc(q)) {
  122. if (ublk_queue_auto_zc_fallback(q))
  123. t->cmd_flags |= UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK;
  124. } else if (!ublk_queue_no_buf(q))
  125. t->cmd_flags |= UBLK_BATCH_F_HAS_BUF_ADDR;
  126. t->state |= UBLKS_T_BATCH_IO;
  127. ublk_log("%s: thread %d commit(nr_bufs %u, buf_size %u, start %u)\n",
  128. __func__, t->idx,
  129. t->nr_commit_buf, t->commit_buf_size,
  130. t->nr_bufs);
  131. }
  132. static void free_batch_fetch_buf(struct ublk_thread *t)
  133. {
  134. int i;
  135. for (i = 0; i < t->nr_fetch_bufs; i++) {
  136. io_uring_free_buf_ring(&t->ring, t->fetch[i].br, 1, i);
  137. munlock(t->fetch[i].fetch_buf, t->fetch[i].fetch_buf_size);
  138. free(t->fetch[i].fetch_buf);
  139. }
  140. free(t->fetch);
  141. }
  142. static int alloc_batch_fetch_buf(struct ublk_thread *t)
  143. {
  144. /* page aligned fetch buffer, and it is mlocked for speedup delivery */
  145. unsigned pg_sz = getpagesize();
  146. unsigned buf_size = round_up(t->dev->dev_info.queue_depth * 2, pg_sz);
  147. int ret;
  148. int i = 0;
  149. /* double fetch buffer for each queue */
  150. t->nr_fetch_bufs = t->nr_queues * 2;
  151. t->fetch = calloc(t->nr_fetch_bufs, sizeof(*t->fetch));
  152. /* allocate one buffer for each queue */
  153. for (i = 0; i < t->nr_fetch_bufs; i++) {
  154. t->fetch[i].fetch_buf_size = buf_size;
  155. if (posix_memalign((void **)&t->fetch[i].fetch_buf, pg_sz,
  156. t->fetch[i].fetch_buf_size))
  157. return -ENOMEM;
  158. /* lock fetch buffer page for fast fetching */
  159. if (mlock(t->fetch[i].fetch_buf, t->fetch[i].fetch_buf_size))
  160. ublk_err("%s: can't lock fetch buffer %s\n", __func__,
  161. strerror(errno));
  162. t->fetch[i].br = io_uring_setup_buf_ring(&t->ring, 1,
  163. i, IOU_PBUF_RING_INC, &ret);
  164. if (!t->fetch[i].br) {
  165. ublk_err("Buffer ring register failed %d\n", ret);
  166. return ret;
  167. }
  168. }
  169. return 0;
  170. }
  171. int ublk_batch_alloc_buf(struct ublk_thread *t)
  172. {
  173. int ret;
  174. ublk_assert(t->nr_commit_buf < 2 * UBLK_MAX_QUEUES);
  175. ret = alloc_batch_commit_buf(t);
  176. if (ret)
  177. return ret;
  178. return alloc_batch_fetch_buf(t);
  179. }
  180. void ublk_batch_free_buf(struct ublk_thread *t)
  181. {
  182. free_batch_commit_buf(t);
  183. free_batch_fetch_buf(t);
  184. }
  185. static void ublk_init_batch_cmd(struct ublk_thread *t, __u16 q_id,
  186. struct io_uring_sqe *sqe, unsigned op,
  187. unsigned short elem_bytes,
  188. unsigned short nr_elem,
  189. unsigned short buf_idx)
  190. {
  191. struct ublk_batch_io *cmd;
  192. __u64 user_data;
  193. cmd = (struct ublk_batch_io *)ublk_get_sqe_cmd(sqe);
  194. ublk_set_sqe_cmd_op(sqe, op);
  195. sqe->fd = 0; /* dev->fds[0] */
  196. sqe->opcode = IORING_OP_URING_CMD;
  197. sqe->flags = IOSQE_FIXED_FILE;
  198. cmd->q_id = q_id;
  199. cmd->flags = 0;
  200. cmd->reserved = 0;
  201. cmd->elem_bytes = elem_bytes;
  202. cmd->nr_elem = nr_elem;
  203. user_data = build_user_data(buf_idx, _IOC_NR(op), nr_elem, q_id, 0);
  204. io_uring_sqe_set_data64(sqe, user_data);
  205. t->cmd_inflight += 1;
  206. ublk_dbg(UBLK_DBG_IO_CMD, "%s: thread %u qid %d cmd_op %x data %lx "
  207. "nr_elem %u elem_bytes %u buf_size %u buf_idx %d "
  208. "cmd_inflight %u\n",
  209. __func__, t->idx, q_id, op, user_data,
  210. cmd->nr_elem, cmd->elem_bytes,
  211. nr_elem * elem_bytes, buf_idx, t->cmd_inflight);
  212. }
  213. static void ublk_setup_commit_sqe(struct ublk_thread *t,
  214. struct io_uring_sqe *sqe,
  215. unsigned short buf_idx)
  216. {
  217. struct ublk_batch_io *cmd;
  218. cmd = (struct ublk_batch_io *)ublk_get_sqe_cmd(sqe);
  219. /* Use plain user buffer instead of fixed buffer */
  220. cmd->flags |= t->cmd_flags;
  221. }
  222. static void ublk_batch_queue_fetch(struct ublk_thread *t,
  223. struct ublk_queue *q,
  224. unsigned short buf_idx)
  225. {
  226. unsigned short nr_elem = t->fetch[buf_idx].fetch_buf_size / 2;
  227. struct io_uring_sqe *sqe;
  228. io_uring_buf_ring_add(t->fetch[buf_idx].br, t->fetch[buf_idx].fetch_buf,
  229. t->fetch[buf_idx].fetch_buf_size,
  230. 0, 0, 0);
  231. io_uring_buf_ring_advance(t->fetch[buf_idx].br, 1);
  232. ublk_io_alloc_sqes(t, &sqe, 1);
  233. ublk_init_batch_cmd(t, q->q_id, sqe, UBLK_U_IO_FETCH_IO_CMDS, 2, nr_elem,
  234. buf_idx);
  235. sqe->rw_flags= IORING_URING_CMD_MULTISHOT;
  236. sqe->buf_group = buf_idx;
  237. sqe->flags |= IOSQE_BUFFER_SELECT;
  238. t->fetch[buf_idx].fetch_buf_off = 0;
  239. }
  240. void ublk_batch_start_fetch(struct ublk_thread *t)
  241. {
  242. int i;
  243. int j = 0;
  244. for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) {
  245. if (t->q_map[i]) {
  246. struct ublk_queue *q = &t->dev->q[i];
  247. /* submit two fetch commands for each queue */
  248. ublk_batch_queue_fetch(t, q, j++);
  249. ublk_batch_queue_fetch(t, q, j++);
  250. }
  251. }
  252. }
  253. static unsigned short ublk_compl_batch_fetch(struct ublk_thread *t,
  254. struct ublk_queue *q,
  255. const struct io_uring_cqe *cqe)
  256. {
  257. unsigned short buf_idx = user_data_to_tag(cqe->user_data);
  258. unsigned start = t->fetch[buf_idx].fetch_buf_off;
  259. unsigned end = start + cqe->res;
  260. void *buf = t->fetch[buf_idx].fetch_buf;
  261. int i;
  262. if (cqe->res < 0)
  263. return buf_idx;
  264. if ((end - start) / 2 > q->q_depth) {
  265. ublk_err("%s: fetch duplicated ios offset %u count %u\n", __func__, start, cqe->res);
  266. for (i = start; i < end; i += 2) {
  267. unsigned short tag = *(unsigned short *)(buf + i);
  268. ublk_err("%u ", tag);
  269. }
  270. ublk_err("\n");
  271. }
  272. for (i = start; i < end; i += 2) {
  273. unsigned short tag = *(unsigned short *)(buf + i);
  274. if (tag >= q->q_depth)
  275. ublk_err("%s: bad tag %u\n", __func__, tag);
  276. if (q->tgt_ops->queue_io)
  277. q->tgt_ops->queue_io(t, q, tag);
  278. }
  279. t->fetch[buf_idx].fetch_buf_off = end;
  280. return buf_idx;
  281. }
  282. static int __ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q)
  283. {
  284. unsigned short nr_elem = q->q_depth;
  285. unsigned short buf_idx = ublk_alloc_commit_buf(t);
  286. struct io_uring_sqe *sqe;
  287. void *buf;
  288. int i;
  289. ublk_assert(buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX);
  290. ublk_io_alloc_sqes(t, &sqe, 1);
  291. ublk_assert(nr_elem == q->q_depth);
  292. buf = ublk_get_commit_buf(t, buf_idx);
  293. for (i = 0; i < nr_elem; i++) {
  294. struct ublk_batch_elem *elem = (struct ublk_batch_elem *)(
  295. buf + i * t->commit_buf_elem_size);
  296. struct ublk_io *io = &q->ios[i];
  297. elem->tag = i;
  298. elem->result = 0;
  299. if (ublk_queue_use_auto_zc(q))
  300. elem->buf_index = ublk_batch_io_buf_idx(t, q, i);
  301. else if (!ublk_queue_no_buf(q))
  302. elem->buf_addr = (__u64)io->buf_addr;
  303. }
  304. sqe->addr = (__u64)buf;
  305. sqe->len = t->commit_buf_elem_size * nr_elem;
  306. ublk_init_batch_cmd(t, q->q_id, sqe, UBLK_U_IO_PREP_IO_CMDS,
  307. t->commit_buf_elem_size, nr_elem, buf_idx);
  308. ublk_setup_commit_sqe(t, sqe, buf_idx);
  309. return 0;
  310. }
  311. int ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q)
  312. {
  313. int ret = 0;
  314. pthread_spin_lock(&q->lock);
  315. if (q->flags & UBLKS_Q_PREPARED)
  316. goto unlock;
  317. ret = __ublk_batch_queue_prep_io_cmds(t, q);
  318. if (!ret)
  319. q->flags |= UBLKS_Q_PREPARED;
  320. unlock:
  321. pthread_spin_unlock(&q->lock);
  322. return ret;
  323. }
  324. static void ublk_batch_compl_commit_cmd(struct ublk_thread *t,
  325. const struct io_uring_cqe *cqe,
  326. unsigned op)
  327. {
  328. unsigned short buf_idx = user_data_to_tag(cqe->user_data);
  329. if (op == _IOC_NR(UBLK_U_IO_PREP_IO_CMDS))
  330. ublk_assert(cqe->res == 0);
  331. else if (op == _IOC_NR(UBLK_U_IO_COMMIT_IO_CMDS)) {
  332. int nr_elem = user_data_to_tgt_data(cqe->user_data);
  333. ublk_assert(cqe->res == t->commit_buf_elem_size * nr_elem);
  334. } else
  335. ublk_assert(0);
  336. ublk_free_commit_buf(t, buf_idx);
  337. }
  338. void ublk_batch_compl_cmd(struct ublk_thread *t,
  339. const struct io_uring_cqe *cqe)
  340. {
  341. unsigned op = user_data_to_op(cqe->user_data);
  342. struct ublk_queue *q;
  343. unsigned buf_idx;
  344. unsigned q_id;
  345. if (op == _IOC_NR(UBLK_U_IO_PREP_IO_CMDS) ||
  346. op == _IOC_NR(UBLK_U_IO_COMMIT_IO_CMDS)) {
  347. t->cmd_inflight--;
  348. ublk_batch_compl_commit_cmd(t, cqe, op);
  349. return;
  350. }
  351. /* FETCH command is per queue */
  352. q_id = user_data_to_q_id(cqe->user_data);
  353. q = &t->dev->q[q_id];
  354. buf_idx = ublk_compl_batch_fetch(t, q, cqe);
  355. if (cqe->res < 0 && cqe->res != -ENOBUFS) {
  356. t->cmd_inflight--;
  357. t->state |= UBLKS_T_STOPPING;
  358. } else if (!(cqe->flags & IORING_CQE_F_MORE) || cqe->res == -ENOBUFS) {
  359. t->cmd_inflight--;
  360. ublk_batch_queue_fetch(t, q, buf_idx);
  361. }
  362. }
  363. static void __ublk_batch_commit_io_cmds(struct ublk_thread *t,
  364. struct batch_commit_buf *cb)
  365. {
  366. struct io_uring_sqe *sqe;
  367. unsigned short buf_idx;
  368. unsigned short nr_elem = cb->done;
  369. /* nothing to commit */
  370. if (!nr_elem) {
  371. ublk_free_commit_buf(t, cb->buf_idx);
  372. return;
  373. }
  374. ublk_io_alloc_sqes(t, &sqe, 1);
  375. buf_idx = cb->buf_idx;
  376. sqe->addr = (__u64)cb->elem;
  377. sqe->len = nr_elem * t->commit_buf_elem_size;
  378. /* commit isn't per-queue command */
  379. ublk_init_batch_cmd(t, cb->q_id, sqe, UBLK_U_IO_COMMIT_IO_CMDS,
  380. t->commit_buf_elem_size, nr_elem, buf_idx);
  381. ublk_setup_commit_sqe(t, sqe, buf_idx);
  382. }
  383. void ublk_batch_commit_io_cmds(struct ublk_thread *t)
  384. {
  385. int i;
  386. for (i = 0; i < t->nr_queues; i++) {
  387. struct batch_commit_buf *cb = &t->commit[i];
  388. if (cb->buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX)
  389. __ublk_batch_commit_io_cmds(t, cb);
  390. }
  391. }
  392. static void __ublk_batch_init_commit(struct ublk_thread *t,
  393. struct batch_commit_buf *cb,
  394. unsigned short buf_idx)
  395. {
  396. /* so far only support 1:1 queue/thread mapping */
  397. cb->buf_idx = buf_idx;
  398. cb->elem = ublk_get_commit_buf(t, buf_idx);
  399. cb->done = 0;
  400. cb->count = t->commit_buf_size /
  401. t->commit_buf_elem_size;
  402. }
  403. /* COMMIT_IO_CMDS is per-queue command, so use its own commit buffer */
  404. static void ublk_batch_init_commit(struct ublk_thread *t,
  405. struct batch_commit_buf *cb)
  406. {
  407. unsigned short buf_idx = ublk_alloc_commit_buf(t);
  408. ublk_assert(buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX);
  409. ublk_assert(!ublk_batch_commit_prepared(cb));
  410. __ublk_batch_init_commit(t, cb, buf_idx);
  411. }
  412. void ublk_batch_prep_commit(struct ublk_thread *t)
  413. {
  414. int i;
  415. for (i = 0; i < t->nr_queues; i++)
  416. t->commit[i].buf_idx = UBLKS_T_COMMIT_BUF_INV_IDX;
  417. }
  418. void ublk_batch_complete_io(struct ublk_thread *t, struct ublk_queue *q,
  419. unsigned tag, int res)
  420. {
  421. unsigned q_t_idx = ublk_queue_idx_in_thread(t, q);
  422. struct batch_commit_buf *cb = &t->commit[q_t_idx];
  423. struct ublk_batch_elem *elem;
  424. struct ublk_io *io = &q->ios[tag];
  425. if (!ublk_batch_commit_prepared(cb))
  426. ublk_batch_init_commit(t, cb);
  427. ublk_assert(q->q_id == cb->q_id);
  428. elem = (struct ublk_batch_elem *)(cb->elem + cb->done * t->commit_buf_elem_size);
  429. elem->tag = tag;
  430. elem->buf_index = ublk_batch_io_buf_idx(t, q, tag);
  431. elem->result = res;
  432. if (!ublk_queue_no_buf(q))
  433. elem->buf_addr = (__u64) (uintptr_t) io->buf_addr;
  434. cb->done += 1;
  435. ublk_assert(cb->done <= cb->count);
  436. }
  437. void ublk_batch_setup_map(unsigned char (*q_thread_map)[UBLK_MAX_QUEUES],
  438. int nthreads, int queues)
  439. {
  440. int i, j;
  441. /*
  442. * Setup round-robin queue-to-thread mapping for arbitrary N:M combinations.
  443. *
  444. * This algorithm distributes queues across threads (and threads across queues)
  445. * in a balanced round-robin fashion to ensure even load distribution.
  446. *
  447. * Examples:
  448. * - 2 threads, 4 queues: T0=[Q0,Q2], T1=[Q1,Q3]
  449. * - 4 threads, 2 queues: T0=[Q0], T1=[Q1], T2=[Q0], T3=[Q1]
  450. * - 3 threads, 3 queues: T0=[Q0], T1=[Q1], T2=[Q2] (1:1 mapping)
  451. *
  452. * Phase 1: Mark which queues each thread handles (boolean mapping)
  453. */
  454. for (i = 0, j = 0; i < queues || j < nthreads; i++, j++) {
  455. q_thread_map[j % nthreads][i % queues] = 1;
  456. }
  457. /*
  458. * Phase 2: Convert boolean mapping to sequential indices within each thread.
  459. *
  460. * Transform from: q_thread_map[thread][queue] = 1 (handles queue)
  461. * To: q_thread_map[thread][queue] = N (queue index within thread)
  462. *
  463. * This allows each thread to know the local index of each queue it handles,
  464. * which is essential for buffer allocation and management. For example:
  465. * - Thread 0 handling queues [0,2] becomes: q_thread_map[0][0]=1, q_thread_map[0][2]=2
  466. * - Thread 1 handling queues [1,3] becomes: q_thread_map[1][1]=1, q_thread_map[1][3]=2
  467. */
  468. for (j = 0; j < nthreads; j++) {
  469. unsigned char seq = 1;
  470. for (i = 0; i < queues; i++) {
  471. if (q_thread_map[j][i])
  472. q_thread_map[j][i] = seq++;
  473. }
  474. }
  475. #if 0
  476. for (j = 0; j < nthreads; j++) {
  477. printf("thread %0d: ", j);
  478. for (i = 0; i < queues; i++) {
  479. if (q_thread_map[j][i])
  480. printf("%03u ", i);
  481. }
  482. printf("\n");
  483. }
  484. printf("\n");
  485. for (j = 0; j < nthreads; j++) {
  486. for (i = 0; i < queues; i++) {
  487. printf("%03u ", q_thread_map[j][i]);
  488. }
  489. printf("\n");
  490. }
  491. #endif
  492. }