ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/Development/ray/src/util/pvsum.c
(Generate patch)

Comparing ray/src/util/pvsum.c (file contents):
Revision 2.8 by greg, Fri Oct 24 22:41:10 2025 UTC vs.
Revision 2.10 by greg, Thu Oct 30 02:48:54 2025 UTC

# Line 35 | Line 35 | char   pixasp[48] = "";                /* PIXASPECT= line from header
35   int     gargc;                          /* global argc */
36   char    **gargv;                        /* global argv */
37  
38 < RMATRIX *cmtx = NULL;                   /* coefficient matrix */
38 > RMATRIX *cmtx;                          /* coefficient matrix */
39 > int     row0, rowN;                     /* rows for current pass */
40  
41   /* does the given spec contain integer format? */
42   int
# Line 125 | Line 126 | get_iotypes(void)
126                  long    data_start = ftell(fp);         /* make sure input flat */
127                  off_t   dend = lseek(fileno(fp), 0, SEEK_END);
128                  if (dend < data_start + 4L*xres*yres) {
129 <                        fputs("Warning - multi-processing requires flat input files\n",
130 <                                        stderr);
129 >                        fprintf(stderr, "%s: warning - multi-processing requires flat input files\n",
130 >                                        gargv[0]);
131                          nprocs = 1;
132                  }
133          }
# Line 143 | Line 144 | get_iotypes(void)
144                  rmx_free(cmtx);
145                  cmtx = nmtx;
146          } else if (cmtx->ncomp != ncomp) {
147 <                fprintf(stderr, "operation %s needs %d components, has %d\n",
148 <                                cmtx->ncols == 1 ? "vector" : "matrix",
147 >                fprintf(stderr, "%s: operation %s needs %d components, has %d\n",
148 >                                gargv[0], cmtx->ncols == 1 ? "vector" : "matrix",
149                                  ncomp, cmtx->ncomp);
150                  return(0);
151          }
152          if ((in_type != DTrgbe) & (in_type != DTxyze) & (in_type != DTspec) &
153                          (in_type != DTfloat)) {
154 <                fprintf(stderr, "%s unsupported input data type: %s\n",
154 >                fprintf(stderr, "%s: unsupported input data type '%s'\n",
155                                  fbuf, cm_fmt_id[in_type]);
156                  return(0);
157          }
# Line 162 | Line 163 | get_iotypes(void)
163          return(1);
164   }
165  
166 + struct hdata {
167 +        int     xr, yr;         /* resolution */
168 +        int     fno;            /* frame # */
169 +        char    fmt[MAXFMTLEN]; /* format */
170 + };
171 +
172   /* check subsequent headers match initial file */
173   int
174   checkline(char *s, void *p)
175   {
176          static int      exposWarned = 0;
177 <        int             *xyres = (int *)p;
171 <        char            fmt[MAXFMTLEN];
177 >        struct hdata    *hp = (struct hdata *)p;
178  
179          if (!strncmp(s, "NCOLS=", 6)) {
180 <                xyres[0] = atoi(s+6);
181 <                if (xyres[0] <= 0)
180 >                hp->xr = atoi(s+6);
181 >                if (hp->xr <= 0)
182                          return(-1);
183                  return(1);
184          }
185          if (!strncmp(s, "NROWS=", 6)) {
186 <                xyres[1] = atoi(s+6);
187 <                if (xyres[1] <= 0)
186 >                hp->yr = atoi(s+6);
187 >                if (hp->yr <= 0)
188                          return(-1);
189                  return(1);
190          }
191 +        if (!strncmp(s, "FRAME=", 6)) {
192 +                hp->fno = atoi(s+6);
193 +                return(1);
194 +        }
195          if (isncomp(s)) {
196                  if (ncompval(s) != ncomp)
197                          return(-1);
# Line 189 | Line 199 | checkline(char *s, void *p)
199          }
200          if (isexpos(s)) {
201                  if (!exposWarned && fabs(1. - exposval(s)) > 0.04) {
202 <                        fputs("Warning - ignoring EXPOSURE setting(s)\n",
203 <                                        stderr);
202 >                        fprintf(stderr, "%s: warning - ignoring EXPOSURE setting(s)\n",
203 >                                        gargv[0]);
204                          exposWarned++;
205                  }
206                  return(1);
207          }
208 <        if (formatval(fmt, s)) {
199 <                if (strcmp(fmt, cm_fmt_id[in_type]))
200 <                        return(-1);
208 >        if (formatval(hp->fmt, s))
209                  return(1);
210 <        }
210 >
211          return(0);
212   }
213  
214 < /* open and check input file */
214 > /* open and check input/output file, read/write mode if fno >= 0 */
215   FILE *
216 < open_input(char *fname)
216 > open_iofile(char *fname, int fno)
217   {
218 <        int     xyres[2];
219 <        FILE    *fp = fopen(fname, "rb");
218 >        struct hdata    hd;
219 >        FILE            *fp = fopen(fname, fno>=0 ? "r+b" : "rb");
220  
221          if (!fp) {
222 <                fprintf(stderr, "%s: cannot open for reading\n", fname);
222 >                fprintf(stderr, "%s: cannot open for reading%s\n",
223 >                                fname, fno>=0 ? "/writing" : "");
224                  return(NULL);
225          }
226 <        xyres[0] = xyres[1] = 0;
227 <        if (getheader(fp, checkline, xyres) < 0) {
226 >        hd.xr = hd.yr = 0;
227 >        hd.fno = -1;
228 >        hd.fmt[0] = '\0';
229 >        if (getheader(fp, checkline, &hd) < 0) {
230                  fprintf(stderr, "%s: bad/inconsistent header\n", fname);
231                  fclose(fp);
232                  return(NULL);
233          }
234 <        if ((xyres[0] <= 0) | (xyres[1] <= 0) &&
235 <                        !fscnresolu(&xyres[0], &xyres[1], fp)) {
234 >        if ((hd.fno >= 0) & (fno >= 0) & (hd.fno != fno)) {
235 >                fprintf(stderr, "%s: unexpected frame number (%d != %d)\n",
236 >                                fname, hd.fno, fno);
237 >                fclose(fp);
238 >                return(NULL);
239 >        }
240 >        if (strcmp(hd.fmt, cm_fmt_id[fno>=0 ? out_type : in_type])) {
241 >                fprintf(stderr, "%s: wrong format\n", fname);
242 >                fclose(fp);
243 >                return(NULL);
244 >        }
245 >        if ((hd.xr <= 0) | (hd.yr <= 0) &&
246 >                        !fscnresolu(&hd.xr, &hd.yr, fp)) {
247                  fprintf(stderr, "%s: missing resolution\n", fname);
248                  fclose(fp);
249                  return(NULL);
250          }
251 <        if ((xyres[0] != xres) | (xyres[1] != yres)) {
251 >        if ((hd.xr != xres) | (hd.yr != yres)) {
252                  fprintf(stderr, "%s: mismatched resolution\n", fname);
253                  fclose(fp);
254                  return(NULL);
# Line 234 | Line 256 | open_input(char *fname)
256          return(fp);
257   }
258  
259 + /* read in previous pixel data from output and rewind to data start */
260 + int
261 + reload_data(float *osum, FILE *fp)
262 + {
263 +        long    dstart;
264 +
265 +        if (!osum | !fp)
266 +                return(0);
267 +        if ((dstart = ftell(fp)) < 0) {
268 +                fprintf(stderr, "%s: ftell() error in reload_data()\n",
269 +                                gargv[0]);
270 +                return(0);
271 +        }
272 +        if (in_type == DTfloat) {
273 +                if (fread(osum, sizeof(float)*ncomp, (size_t)xres*yres, fp) !=
274 +                                (size_t)xres*yres) {
275 +                        fprintf(stderr, "%s: fread() error\n", gargv[0]);
276 +                        return(0);
277 +                }
278 +        } else {
279 +                int     y;
280 +                for (y = 0; y < yres; y++, osum += ncomp*xres)
281 +                        if (freadsscan(osum, ncomp, xres, fp) < 0) {
282 +                                fprintf(stderr, "%s: freadsscan() error\n", gargv[0]);
283 +                                return(0);
284 +                        }
285 +        }
286 +        if (fseek(fp, dstart, SEEK_SET) < 0) {
287 +                fprintf(stderr, "%s: fseek() error in reload_data()\n",
288 +                                gargv[0]);
289 +                return(0);
290 +        }
291 +        return(1);
292 + }
293 +
294   /* open output file or command (if !NULL) and write info header */
295   FILE *
296   open_output(char *ospec, int fno)
# Line 245 | Line 302 | open_output(char *ospec, int fno)
302                  fp = stdout;
303          } else if (ospec[0] == '!') {
304                  if (!(fp = popen(ospec+1, "w"))) {
305 <                        fprintf(stderr, "Cannot start: %s\n", ospec);
305 >                        fprintf(stderr, "%s: cannot start: %s\n", gargv[0], ospec);
306                          return(NULL);
307                  }
308          } else if (!(fp = fopen(ospec, "w"))) {
# Line 292 | Line 349 | open_output(char *ospec, int fno)
349                  fprtresolu(xres, yres, fp);
350                  break;
351          default:
352 <                fputs("Unsupported output type!\n", stderr);
352 >                fprintf(stderr, "%s: unsupported output type!\n", gargv[0]);
353                  return(NULL);
354          }
355          if (fflush(fp) < 0) {
# Line 313 | Line 370 | solo_process(void)
370          int     c;
371  
372          if (!osum | !iscan) {
373 <                fprintf(stderr, "Cannot allocate %dx%d %d-component accumulator\n",
374 <                                xres, yres, ncomp);
373 >                fprintf(stderr, "%s: annot allocate %dx%d %d-component accumulator\n",
374 >                                gargv[0], xres, yres, ncomp);
375                  return(0);
376          }
377          if (sizeof(float) != sizeof(COLORV)) {
378 <                fputs("Code Error 1 in solo_process()\n", stderr);
378 >                fprintf(stderr, "%s: Code Error 1 in solo_process()\n", gargv[0]);
379                  return(0);
380          }
381 <        for (c = 0; c < cmtx->ncols; c++) {     /* run through each column/output */
381 >                                        /* run through each column/output */
382 >        for (c = 0; c < cmtx->ncols; c++) {
383 >                int     rc = rowN - row0;
384                  FILE    *fout;
385                  int     y;
386 <                int     rc = cmtx->nrows;
387 <                if (c > 0)              /* clear accumulator? */
386 >                                        /* open output (load if multipass) */
387 >                if (out_spec) {         /* file or command */
388 >                        if (cmtx->ncols > 1 && !hasFormat(out_spec)) {
389 >                                fprintf(stderr, "%s: sequential result must go to stdout\n",
390 >                                                gargv[0]);
391 >                                return(0);
392 >                        }
393 >                        sprintf(fbuf, out_spec, c);
394 >                        if (row0) {     /* another pass -- get prev. data */
395 >                                fout = open_iofile(fbuf, c);
396 >                                if (!reload_data(osum, fout))
397 >                                        return(0);
398 >                        } else          /* else new output (clobber prev. files) */
399 >                                fout = open_output(fbuf, c-(cmtx->ncols==1));
400 >                } else {                        /* else stdout */
401 >                        if ((out_type == DTfloat) & (cmtx->ncols > 1)) {
402 >                                fprintf(stderr, "%s: float outputs must have separate destinations\n",
403 >                                                gargv[0]);
404 >                                return(0);
405 >                        }
406 >                        strcpy(fbuf, "<stdout>");
407 >                        fout = open_output(NULL, c-(cmtx->ncols==1));
408 >                }
409 >                if (!fout)
410 >                        return(0);      /* assume error was reported */
411 >                if (!row0 & (c > 0))    /* clear accumulator? */
412                          memset(osum, 0, sizeof(float)*ncomp*xres*yres);
413                  while (rc-- > 0) {      /* run through each input file */
414 <                        const int       r = c&1 ? rc : cmtx->nrows-1 - rc;
414 >                        const int       r = c&1 ? row0 + rc : rowN-1 - rc;
415                          const rmx_dtype *cval = rmx_val(cmtx, r, c);
416                          FILE            *finp;
417                          int             i, x;
# Line 337 | Line 420 | solo_process(void)
420                          if (i < 0)      /* this coefficient is zero, skip */
421                                  continue;
422                          sprintf(fbuf, in_spec, r);
423 <                        finp = open_input(fbuf);
423 >                        finp = open_iofile(fbuf, -1);
424                          if (!finp)
425                                  return(0);
426                          for (y = 0; y < yres; y++) {
# Line 353 | Line 436 | solo_process(void)
436                                                  dst[i] += cval[i]*iscan[x*ncomp + i];
437                          }
438                          fclose(finp);
439 <                }                       /* write out accumulated column result... */
357 <                if (out_spec) {         /* ...to file or command */
358 <                        if (cmtx->ncols > 1 && !hasFormat(out_spec)) {
359 <                                fputs("Sequential result must go to stdout\n", stderr);
360 <                                return(0);
361 <                        }
362 <                        sprintf(fbuf, out_spec, c);
363 <                        fout = open_output(fbuf, c-(cmtx->ncols==1));
364 <                } else {                /* ...to stdout */
365 <                        if ((out_type == DTfloat) & (cmtx->ncols > 1)) {
366 <                                fputs("Float outputs must have separate destinations\n",
367 <                                                stderr);
368 <                                return(0);
369 <                        }
370 <                        strcpy(fbuf, "<stdout>");
371 <                        fout = open_output(NULL, c-(cmtx->ncols==1));
372 <                }
373 <                if (!fout)
374 <                        return(0);      /* assume error was reported */
439 >                }                       /* write accumulated picture */
440                  if (out_type != DTfloat) {
441                          for (y = 0; y < yres; y++)
442                                  if (fwritesscan(osum + (size_t)y*xres*ncomp,
# Line 383 | Line 448 | solo_process(void)
448  
449                  if (fbuf[0] == '!') {
450                          if (pclose(fout) != 0) {
451 <                                fprintf(stderr, "Bad status from: %s\n", fbuf);
451 >                                fprintf(stderr, "%s: bad status from: %s\n", gargv[0], fbuf);
452                                  return(0);
453                          }
454                  } else if (fout != stdout && fclose(fout) == EOF)
# Line 412 | Line 477 | scramble(int n)
477          int     i;
478  
479          if (!scarr) {
480 <                fprintf(stderr, "Out of memory in scramble(%d)\n", n);
480 >                fprintf(stderr, "%s: out of memory in scramble(%d)\n", gargv[0], n);
481                  exit(1);
482          }
483          for (i = n; i--; )
# Line 433 | Line 498 | multi_process(void)
498   {
499          int     coff = nprocs;
500          int     odd = 0;
501 +        float   *osum = NULL;
502 +        int     *syarr = NULL;
503          char    fbuf[512];
437        float   *osum;
438        int     *syarr;
504          int     c;
505                                          /* sanity check */
506          if (sizeof(float) != sizeof(COLORV)) {
507 <                fputs("Code Error 1 in multi_process()\n", stderr);
507 >                fprintf(stderr, "%s: code Error 1 in multi_process()\n", gargv[0]);
508                  return(0);
509          }
510 <        while (--coff > 0) {            /* parent births children */
510 >        fflush(NULL);                   /* parent births helper subprocs */
511 >        while (--coff > 0) {
512                  int     pid = fork();
513                  if (pid < 0) {
514 <                        fputs("fork() call failed!\n", stderr);
514 >                        fprintf(stderr, "%s: fork() call failed!\n", gargv[0]);
515                          return(0);
516                  }
517 <                if (pid == 0) break;    /* child gets to work */
517 >                if (!pid) break;        /* new child gets to work */
518          }
519 <        osum = (float *)calloc((size_t)xres*yres, sizeof(float)*ncomp);
520 <        if (!osum) {
521 <                fprintf(stderr, "Cannot allocate %dx%d %d-component accumulator\n",
522 <                                xres, yres, ncomp);
523 <                return(0);
519 >        if (!row0 | (out_type != DTfloat)) {
520 >                osum = (float *)calloc((size_t)xres*yres, sizeof(float)*ncomp);
521 >                if (!osum) {
522 >                        fprintf(stderr, "%s: cannot allocate %dx%d %d-component accumulator\n",
523 >                                        gargv[0], xres, yres, ncomp);
524 >                        return(0);
525 >                }
526          }
527          srandom(113*coff + 5669);       /* randomize row access for this process */
528          syarr = scramble(yres);
529                                          /* run through our unique set of columns */
530          for (c = coff; c < cmtx->ncols; c += nprocs) {
531 +                int     rc = rowN - row0;
532 +                void    *omap = NULL;
533 +                size_t  omaplen = 0;
534 +                long    dstart;
535                  FILE    *fout;
536                  int     y;
537 <                int     rc = cmtx->nrows;
538 <                if (c > coff)           /* clear accumulator? */
539 <                        memset(osum, 0, sizeof(float)*ncomp*xres*yres);
537 >                                        /* create/load output */
538 >                sprintf(fbuf, out_spec, c);
539 >                if (row0) {             /* making another pass? */
540 >                        fout = open_iofile(fbuf, c);
541 >                        if (!fout) return(0);
542 >                        if (out_type == DTfloat) {
543 >                                dstart = ftell(fout);
544 >                                if ((dstart < 0) | (dstart % sizeof(float))) {
545 >                                        fprintf(stderr, "%s: bad seek/alignment\n", fbuf);
546 >                                        return(0);
547 >                                }
548 >                                omaplen = dstart + sizeof(float)*ncomp*xres*yres;
549 >                                omap = mmap(NULL, omaplen, PROT_READ|PROT_WRITE,
550 >                                                MAP_FILE|MAP_SHARED, fileno(fout), 0);
551 >                                if (omap == MAP_FAILED) {
552 >                                        fprintf(stderr, "%s: cannot map file '%s'\n",
553 >                                                        gargv[0], fbuf);
554 >                                        return(0);
555 >                                }
556 >                                osum = (float *)((char *)omap + dstart);
557 >                        } else if (!reload_data(osum, fout))
558 >                                return(0);
559 >                } else {                /* else new output (clobber prev. files) */
560 >                        fout = open_output(fbuf, c);
561 >                        if (!fout) return(0);
562 >                        if (c > coff)   /* clear accumulator? */
563 >                                memset(osum, 0, sizeof(float)*ncomp*xres*yres);
564 >                }
565                  while (rc-- > 0) {      /* map & sum each input file */
566 <                        const int       r = odd ? rc : cmtx->nrows-1 - rc;
566 >                        const int       r = odd ? row0 + rc : rowN-1 - rc;
567                          const rmx_dtype *cval = rmx_val(cmtx, r, c);
568 <                        long            dstart;
472 <                        size_t          maplen;
568 >                        size_t          imaplen;
569                          void            *imap;
570                          FILE            *finp;
571                          float           *dst;
# Line 479 | Line 575 | multi_process(void)
575                          if (i < 0)      /* this coefficient is zero, skip */
576                                  continue;
577                          sprintf(fbuf, in_spec, r);
578 <                        finp = open_input(fbuf);
578 >                        finp = open_iofile(fbuf, -1);
579                          if (!finp)
580                                  return(0);
581                          dstart = ftell(finp);
# Line 492 | Line 588 | multi_process(void)
588                                  return(0);
589                          }
590                          i = in_type==DTfloat ? ncomp*(int)sizeof(float) : ncomp+1;
591 <                        maplen = dstart + (size_t)yres*xres*i;
592 <                        imap = mmap(NULL, maplen, PROT_READ,
591 >                        imaplen = dstart + (size_t)yres*xres*i;
592 >                        imap = mmap(NULL, imaplen, PROT_READ,
593                                          MAP_FILE|MAP_SHARED, fileno(finp), 0);
594                          fclose(finp);           /* will read from map (randomly) */
595                          if (imap == MAP_FAILED) {
# Line 520 | Line 616 | multi_process(void)
616                                          dst[i] += cval[i]*(cvp[i]+(rmx_dtype).5)*fe;
617                                  }
618                              }
619 <                        munmap(imap, maplen);
620 <                }                       /* write accumulated column picture/matrix */
525 <                sprintf(fbuf, out_spec, c);
526 <                fout = open_output(fbuf, c);
527 <                if (!fout)
528 <                        return(0);      /* assume error was reported */
619 >                        munmap(imap, imaplen);
620 >                }                       /* write accumulated column picture */
621                  if (out_type != DTfloat) {
622                          for (y = 0; y < yres; y++)
623                                  if (fwritesscan(osum + (size_t)y*xres*ncomp,
624                                                          ncomp, xres, fout) < 0)
625                                          goto writerr;
626 +                } else if (omap) {
627 +                        if (munmap(omap, omaplen) < 0)
628 +                                goto writerr;
629 +                        osum = NULL;
630                  } else if (fwrite(osum, sizeof(float)*ncomp, (size_t)xres*yres, fout) !=
631                                          (size_t)xres*yres)
632                          goto writerr;
633  
634                  if (fbuf[0] == '!') {
635                          if (pclose(fout) != 0) {
636 <                                fprintf(stderr, "Bad status from: %s\n", fbuf);
636 >                                fprintf(stderr, "%s: bad status from: %s\n", gargv[0], fbuf);
637                                  return(0);
638                          }
639                  } else if (fclose(fout) == EOF)
640                          goto writerr;
641                  odd = !odd;             /* go back & forth to milk page cache */
642          }
643 <        free(osum);
643 >        if (coff) _exit(0);             /* child exits here */
644 >                                        /* but parent waits for children */
645 >        if (osum) free(osum);
646          free(syarr);
647 <        if (coff)                       /* child processes return here... */
550 <                return(1);
551 <        c = 0;                          /* ...but parent waits for children */
647 >        c = 0;
648          while (++coff < nprocs) {
649                  int     st;
650 <                if (wait(&st) < 0)
650 >                if (wait(&st) < 0) {
651 >                        fprintf(stderr, "%s: warning - wait() call failed unexpectedly\n", gargv[0]);
652                          break;
653 +                }
654                  if (st) c = st;
655          }
656          return(c == 0);
# Line 566 | Line 664 | writerr:
664   int
665   main(int argc, char *argv[])
666   {
667 +        double  cacheGB = 0;
668 +        int     rintvl;
669          int     a;
670  
671          gargc = argc;                   /* for header output */
# Line 588 | Line 688 | main(int argc, char *argv[])
688                                  goto badopt;
689                          }
690                          break;
691 +                case 'm':               /* cache size in GigaBytes */
692 +                        cacheGB = atof(argv[++a]);
693 +                        break;
694                  case 'N':               /* number of desired processes */
695                  case 'n':               /* quietly supported alternate */
696                          nprocs = atoi(argv[++a]);
# Line 605 | Line 708 | badopt:                        fprintf(stderr, "%s: bad option: %s\n", argv
708          cmtx = rmx_load(argv[a+1]);     /* loads from stdin if a+1==argc */
709          if (cmtx == NULL)
710                  return(1);              /* error reported */
711 +        cacheGB *= (cmtx->ncols > 1);
712 +        if (cacheGB > 0 && (!out_spec || *out_spec == '!')) {
713 +                fprintf(stderr, "%s: -m option incompatible with output to %s\n",
714 +                                argv[0], out_spec ? "command" : "stdout");
715 +                return(1);
716 +        }
717          if (nprocs > cmtx->ncols)
718                  nprocs = cmtx->ncols;
719   #if defined(_WIN32) || defined(_WIN64)
# Line 631 | Line 740 | badopt:                        fprintf(stderr, "%s: bad option: %s\n", argv
740          }
741          if (!get_iotypes())
742                  return(1);
743 <        if (!(nprocs == 1 ? solo_process() : multi_process()))
744 <                return(1);
743 >        if (cacheGB > 1e-4) {           /* figure out # of passes => rintvl */
744 >                size_t  inp_bytes = (in_type==DTfloat ? sizeof(float)*ncomp
745 >                                                : (size_t)(ncomp+1)) * xres*yres;
746 >                size_t  mem_bytes = sizeof(float)*ncomp*xres*yres;
747 >                int     npasses = (double)inp_bytes*cmtx->nrows /
748 >                                (cacheGB*(1L<<30) - (double)mem_bytes*nprocs) + 1;
749 >                if ((npasses <= 0) | (npasses*8 >= cmtx->nrows))
750 >                        npasses = 1;    /* let's not go there... */
751 >                rintvl = cmtx->nrows / npasses;
752 >                rintvl += (rintvl*npasses < cmtx->nrows);
753 >        } else
754 >                rintvl = cmtx->nrows;
755 >                                        /* make our passes */
756 >        for (row0 = 0; row0 < cmtx->nrows; row0 += rintvl) {
757 >                if ((rowN = row0 + rintvl) > cmtx->nrows)
758 >                        rowN = cmtx->nrows;
759 >                if (nprocs==1 ? !solo_process() : !multi_process())
760 >                        return(1);
761 >        }
762          return(0);
763   userr:
764 <        fprintf(stderr, "Usage: %s [-oc | -of][-o ospec][-N nproc] inpspec [mtx]\n",
764 >        fprintf(stderr, "Usage: %s [-oc | -of][-o ospec][-N nproc][-m cacheGB] inpspec [mtx]\n",
765                                  argv[0]);
766          return(1);
767   }

Diff Legend

Removed lines
+ Added lines
< Changed lines (old)
> Changed lines (new)