The Computer Language
24.11 Benchmarks Game

fasta C gcc #4 program

source code

/* The Computer Language Benchmarks Game
 * https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
 *
 * This benchmark uses ~line_length * sequence_lenth bytes from the
 * random number generator, and approaches the I/O limits of simple
 * write() calls from one thread.  It does this by exploiting the
 * periodicity of the random number generator.  Any language could pay
 * a similar startup cost and then issue these large system write, so
 * it's probably not terribly interesting as a language comparison
 * benchmark.  The calculations are simple, so approaching this number
 * should be a goal for any high performance implementation.  This
 * shows how close each other language and implementation gets to the
 * limit.
 *
 * perf stat
 *    452,094,945      cycles                    #    4.119 GHz
 *    512,589,831      instructions              #    1.13  insn per cycle
 *
 * Nearly all of that is system time.  Writing to /dev/null it almost
 * vanishes.  Parallel writes or making use of filesystem block
 * duplication could potentially let the kernel portion finish faster,
 * but the benchmark is to write to stdout, so this seems done.
 *
 * by Drake Diedrich
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define IM 139968
#define IA   3877
#define IC  29573
#define SEED   42

static uint32_t seed = SEED;
#define rand_next() ( (seed * IA + IC ) % IM )
#define rand_advance() ( seed = rand_next() )
#define rand_seed(x) seed=x

/* per
 * https://en.wikipedia.org/wiki/Modular_exponentiation#Pseudocode */
uint32_t modular_pow(uint64_t base, uint64_t exponent, const uint32_t modulus) {
  if (modulus == 1) {
    return 0;
  }
  uint64_t result = 1;
  base = base % modulus;
  while (exponent > 0) {
    if ((exponent & 1) == 1) {
      result = (result * base) % modulus;
    }
    exponent = exponent >> 1;
    base = (base * base) % modulus;
  }
  return result;
}


/* per
 * https://www.nayuki.io/page/fast-skipping-in-a-linear-congruential-generator
 */
#define IA1 (IA-1)
#define IMA (IA1 * IM)

static uint32_t rand_skip(int n) {
  uint64_t y = ((modular_pow(IA, n, IMA) - 1) / IA1) * IC;
  uint64_t z = modular_pow(IA, n, IM) * seed;
  seed = (y + z) % IM;
  return seed;
}


#define BUFLINES 100

static const char alu[] =
  "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGG"
  "GAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGA"
  "CCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAAT"
  "ACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCA"
  "GCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGG"
  "AGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCC"
  "AGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAA";

static const char iub[] = "acgtBDHKMNRSVWY";
static const float iub_p[] = {
  0.27,
  0.12,
  0.12,
  0.27,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02,
  0.02 };

static const char homosapiens[] = "acgt";
static const float homosapiens_p[] = {
  0.3029549426680,
  0.1979883004921,
  0.1975473066391,
  0.3015094502008
};

#define LINELEN 60
static int buffer_lines_size(const int seqlen) {
  return (LINELEN+1) * seqlen;
}

static char * build_buffer_lines(const char *seq, const int len) {
  int buflen1 = len + LINELEN;
  char *buffer1 = malloc(buflen1);
  int i;
  int whole_seqs = buflen1/len;
  for (i=0; i < whole_seqs; i++) memcpy(buffer1+i*len, seq, len);
  memcpy(buffer1 + whole_seqs*len, seq, buflen1 - whole_seqs*len);

  int buflen2 = buffer_lines_size(len);
  char *buffer2 = malloc(buflen2);
  for (i=0; i<len; i++) {
    memcpy(buffer2+i*(LINELEN+1), buffer1+((i*LINELEN)%len), LINELEN);
    buffer2[(i+1)*(LINELEN+1)-1] = '\n';
  }

  free(buffer1);
  return buffer2;
}

static void repeat_fasta(const char *seq,
			 const int len,
			 const int n) {
  ssize_t wrote = 0;

  char *buffer2 = build_buffer_lines(seq, len);
  const int buflen2 = buffer_lines_size(len);

  int whole_buffers = n / (len*LINELEN);
  for (int i=0; i< whole_buffers; i++) wrote += write(1, buffer2, buflen2);

  int data_remaining = n - whole_buffers * len * LINELEN;
  int embedded_newlines = data_remaining / LINELEN;
  wrote += write(1, buffer2, data_remaining + embedded_newlines);

  free(buffer2);
  if (n % LINELEN != 0) wrote += write(1, "\n", 1);
}

static char * build_hash(const char *symb,const float *probability) {
  int i,j;
  float sum = 0.0;
  const int len = strlen(symb);

  char *hash = malloc(IM);
  if (!hash) exit(-1);

  sum = probability[0];
  for (i=0,j=0;i<IM && j<len;i++) {
    float r = 1.0 * i / IM;
    if (r>=sum) {
      j++;
      sum += probability[j];
    }
    hash[i] = symb[j];
  }
  return hash;
}

static char * build_periodic_sequence(const char *hash, const int len) {
  int i;
  char *sequence = malloc(len);
  if (!sequence) exit(-1);
  for (i=0; i<len; i++) {
    uint32_t v = rand_advance();
    sequence[i] = hash[v];
  }
  return sequence;
}

static void random_fasta(const char *symb,
			 const float *probability,
			 const int n) {
  char *hash = build_hash(symb,probability);
  if (n>IM) {
    char *sequence = build_periodic_sequence(hash, IM);
    free(hash);
    repeat_fasta(sequence, IM, n);
    free(sequence);
  } else {
    char *sequence = build_periodic_sequence(hash, n);
    free(hash);
    repeat_fasta(sequence, n, n);
    free(sequence);
  }

  /* we might not have pulled enough random numbers to stay in the
   * same sequence as other benchmarks, so catch up */
  int skip = n - IM;
  if (skip > 0) {
    rand_skip(skip);
  } else if (skip < 0) {
    rand_seed(SEED);
    rand_skip(n);
  }
}

const char header1[] = ">ONE Homo sapiens alu\n";
const char header2[] = ">TWO IUB ambiguity codes\n";
const char header3[] = ">THREE Homo sapiens frequency\n";

int main(int argc, char **argv) {
  int n=1000;
  if (argc>1) n = atoi(argv[1]);

  write(1, header1, sizeof(header1)-1);
  repeat_fasta(alu, sizeof(alu)-1, n*2);

  write(1, header2, sizeof(header2)-1);
  random_fasta(iub, iub_p, n*3);
  
  write(1, header3, sizeof(header3)-1);
  random_fasta(homosapiens, homosapiens_p, n*5);

  return 0;
// 0.02 secs }
    

notes, command-line, and program output

NOTES:
64-bit Ubuntu quad core
gcc (Ubuntu 14.2.0-4ubuntu2) 14.2.0


 Mon, 09 Dec 2024 19:07:14 GMT

MAKE:
/usr/bin/gcc -pipe -Wall -O3 -fomit-frame-pointer -march=ivybridge  fasta.gcc-4.c -o fasta.gcc-4.gcc_run 
fasta.gcc-4.c: In function ‘main’:
fasta.gcc-4.c:231:3: error: expected declaration or statement at end of input
  231 |   return 0;
      |   ^~~~~~
fasta.gcc-4.c:222:3: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
  222 |   write(1, header1, sizeof(header1)-1);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
fasta.gcc-4.c:225:3: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
  225 |   write(1, header2, sizeof(header2)-1);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
fasta.gcc-4.c:228:3: warning: ignoring return value of ‘write’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
  228 |   write(1, header3, sizeof(header3)-1);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
make: [/home/dunham/all-benchmarksgame/2000-benchmarksgame/nanobench/makefiles/u64q.programs.Makefile:43: fasta.gcc-4.gcc_run] Error 1 (ignored)
rm fasta.gcc-4.c

1.60s to complete and log all make actions

COMMAND LINE:
 ./fasta.gcc-4.gcc_run 250000

MAKE ERROR