Kマーを数える


8

タスクは、k = 1,2,3,4、.....の場合、長さkの異なる部分文字列の数をカウントすることです。

出力

出力行ごとにk1つの数で完了するために、管理ごとに1行を出力する必要があります。出力はk、時間がなくなるまで増加する順序である必要があります。

スコア

あなたのスコアは、1分以内に私のコンピューターで取得できる最高のkです。

入力としてhttp://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/chr2.fa.gzを使用し、改行は無視してください。ただし、コードでは大文字と小文字が区別されます。

タイミングを開始する前に入力を解凍できます。

次の(非効率的な)コードは、異なる4マーの数をカウントします。

awk -v substr_length=4 '(len=length($0))>=substr_length{for (i=1; (i-substr_length)<len; i++) substrs[substr($0,i,substr_length)]++}; END{for (i in substrs) print substrs[i], i}' file.txt|wc

メモリ制限

コードを私のコンピューター上で適切に動作させ、タスクをより困難にするために、使用できるRAMを2GBに制限します。

ulimit -v 2000000

コードを実行する前に。これはRAMの使用を制限する確固たる方法ではないので、新しいプロセスを生成するなど、この制限を回避する想像力に富んだ方法を使用しないでください。もちろんマルチスレッドのコードを書くこともできますが、誰かがそうした場合、そのために使用するRAMの合計を制限する方法を学ぶ必要があります。

タイ・ブレーカー

ある最大の引き分けの場合k、出力を最大にk+1して最速のものが勝つまでにかかる時間を測定します。同時に実行されて1秒以内であるk+1場合は、最初の送信が優先されます。

言語とライブラリ

自由に利用できるコンパイラ/インタプリタ/などがある任意の言語を使用できます。LinuxおよびLinuxでも無料で利用できるライブラリの場合。

私のマシン タイミングは私のマシンで実行されます。これは、Asus M5A78L-M / USB3マザーボード(ソケットAM3 +、8GB DDR3)上のAMD FX-8350 8コアプロセッサに標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。結果として、簡単に入手できる無料のソフトウェアのみを使用し、コードをコンパイルして実行する方法の完全な指示を含めてください。


テスト出力

FUZxxlのコードは次のように出力します(ただし1分以内にすべてではありません)。これは正しいと思います。

14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234

リーグテーブル

  • k> = 4000 FUZxxl(C)
  • k = 16、Keith Randall(C ++)
  • k = 10 FUZxxl(C)

コードを入力にどの程度特化できますか?

  • 答えを事前に計算して、コードに出力させただけでは、明らかに競争が台無しになるでしょう。それをしないでください。
  • 理想的には、コードが実行時に学習できるより迅速に実行するために使用するデータについて、コードが学習する必要があるすべてのものです。
  • ただし、入力はhttp://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/にある* .faファイルのデータのように見えると想定できます。

ではJ、単純な解決策は単純に `[:〜。]`ですが、それでうまくいくとは限りません。
FUZxxl 2015

@FUZxxlええと... 1分間でどのくらいの大きさのakが得られ、多くのRAMが使用されますか?

まあ、2の場合は約5秒で戻り、3の場合は3.2 GBのRAMが必要で、約1分かかります。私はそれが4のために何をするかを試みませんでした。試してみましょう...
2015

@FUZxxlええと...お気軽に回答として提出してください:)私が2GBのRAMで切り取ったことを思い出してください。

1
@Lembik教えません。私は現在、ソリューションを変更している最中であるため、出力を早期に提供します(ただし、全体的に低速です)。私のマシンでデータを前処理するには9分かかります。
FUZxxl 2015

回答:


7

C(≥4000)

このコードは、2 GB未満のRAMを使用する(少し多く使用する)ことも、最初の1分間で出力を生成することもありません。しかし、約6分待つと、すべてk -merカウントが一度に出力されます

kマーをカウントする最高のkを制限するオプションが含まれています。kが妥当な範囲に制限されている場合、コードは1分以内に終了し、2 GiB未満のRAMを使用します。OPはこのソリューションを評価し、4000を大幅に超えない限度で1分未満で終了します。

どのように機能しますか?

アルゴリズムには4つのステップがあります。

  1. 入力ファイルをバッファに読み込み、改行を取り除きます。
  2. インデックスの配列を入力バッファーにサフィックスソートします。たとえば、文字列のサフィックスは次のmississippiとおりです。

    mississippi
    ississippi
    ssissippi
    sissippi
    issippi
    ssippi
    sippi
    ippi
    ppi
    pi
    i
    

    辞書式順にソートされたこれらの文字列は次のとおりです。

    i
    ippi
    issippi
    ississippi
    mississippi
    pi
    ppi
    sippi
    sissippi
    ssippi
    ssissippi
    

    すべてのkについて長さkのすべての等しい部分文字列が、サフィックスでソートされた配列の隣接するエントリにあることが簡単にわかります。

  3. 整数配列が入力され、各インデックスkに個別のkマーの数を格納します。これは、プロセスをスピードアップするために少し複雑な方法で行われます。2つの隣接するエントリをサフィックスでソートされた配列と見なします。

       p     l
       v     v
    issippi
    ississippi
    

    pは2つのエントリの最長共通プレフィックスの長さを示し、lは2番目のエントリの長さを示します。例えば、A対について、我々は、長さの新しい別個のサブストリング検索Kのp < Kリットル。以来、P « Lはしばしば保持し、各ペアの配列多数のエントリをインクリメントすることは非現実的です。代わりに、配列を差分配列として格納します。ここで、各エントリkは、k -merの数と(k  -1)-mer の数の差を示します。これはフォームの更新をオンにします

    0  0  0  0 +1 +1 +1 +1 +1 +1  0  0  0
    

    フォームのはるかに高速な更新に

    0  0  0  0 +1  0  0  0  0  0 -1  0  0
    

    lは常に異なり、実際にはすべての0 < l < nが正確に1回出現することを注意深く観察することで、差を省略し、代わりに差から金額に変換するときに各差から1を差し引くことができます。

  4. 差異は金額に変換されて印刷されます。

ソースコード

このソースは、接尾辞配列のソートにlibdivsufsortを使用します。この呼び出しでコンパイルすると、コードは仕様に従って出力を生成します。

cc -O -o dsskmer dsskmer.c -ldivsufsort

または、次の呼び出しでコンパイルすると、コードはバイナリ出力を生成できます。

cc -O -o dsskmer -DBINOUTPUT dsskmer.c -ldivsufsort

最高制限するKれるk個の -mersがカウントされる、供給-DICAP = K kが限界であるが。

cc -O -o dsskmer -DICAP=64 dsskmer.c -ldivsufsort

-O3コンパイラがこのオプションを提供している場合は、コンパイルしてください。

#include <stdlib.h>
#include <stdio.h>

#include <divsufsort.h>

#ifndef BINOUTPUT
static char stdoutbuf[1024*1024];
#endif

/*
 * load input from stdin and remove newlines. Save length in flen.
 */
static unsigned char*
input(flen)
    int *flen;
{
    size_t n, len, pos, i, j;
    off_t slen;
    unsigned char *buf, *sbuf;

    if (fseek(stdin, 0L, SEEK_END) != 0) {
        perror("Cannot seek stdin");
        abort();
    }

    slen = ftello(stdin);
    if (slen == -1) {
        perror("Cannot tell stdin");
        abort();
    }

    len = (size_t)slen;

    rewind(stdin);

    /* Prepare for one extra trailing \0 byte */
    buf = malloc(len + 1);
    if (buf == NULL) {
        perror("Cannot malloc");
        abort();
    }

    pos = 0;

    while ((n = fread(buf + pos, 1, len - pos, stdin)) != 0)
        pos += n;

    if (ferror(stdin)) {
        perror("Cannot read from stdin");
        abort();
    }

    /* remove newlines */
    for (i = j = 0; i < len; i++)
        if (buf[i] != '\n')
            buf[j++] = buf[i];

    /* try to reclaim some memory */
    sbuf = realloc(buf, j);
    if (sbuf == NULL)
        sbuf = buf;

    *flen = (int)j;
    return sbuf;
}

/*
 * Compute for all k the number of k-mers. kmers will contain at index i the
 * number of (i + 1) mers. The count is computed as an array of differences,
 * where kmers[i] == kmersum[i] - kmersum[i-1] + 1 and then summed up by the
 * caller. This algorithm is a little bit unclear, but when you write subsequent
 * suffixes of an array on a piece of paper, it's easy to see how and why it
 * works.
 */
static void
count(buf, sa, kmers, n)
    const unsigned char *buf;
    const int *sa;
    int *kmers;
{
    int i, cl, cp;

    /* the first item needs special treatment */
    kmers[0]++;

    for (i = 1; i < n; i++) {
        /* The longest common prefix of the two suffixes */
        cl = n - (sa[i-1] > sa[i] ? sa[i-1] : sa[i]);
#ifdef ICAP
        cl = (cl > ICAP ? ICAP : cl);
#endif
        for (cp = 0; cp < cl; cp++)
            if (buf[sa[i-1] + cp] != buf[sa[i] + cp])
                break;

        /* add new prefixes to the table */
        kmers[cp]++;
    }
}

extern int
main()
{
    unsigned char *buf;
    int blen, ilen, *sa, *kmers, i;

    buf = input(&blen);

    sa = malloc(blen * sizeof *sa);

    if (divsufsort(buf, sa, blen) != 0) {
        puts("Cannot divsufsort");
        abort();
    }

#ifdef ICAP
    ilen = ICAP;
    kmers = calloc(ilen + 1, sizeof *kmers);
#else
    ilen = blen;
    kmers = calloc(ilen, sizeof *kmers);
#endif

    if (kmers == NULL) {
        perror("Cannot malloc");
        abort();
    }

    count(buf, sa, kmers, blen);

#ifndef BINOUTPUT
    /* sum up kmers differences */
    for (i = 1; i < ilen; i++)
        kmers[i] += kmers[i-1] - 1;


    /* enlarge buffer of stdout for better IO performance */
    setvbuf(stdout, stdoutbuf, _IOFBF, sizeof stdoutbuf);

    /* human output */
    for (i = 0; i < ilen; i++)
        printf("%d\n", kmers[i]);
#else
    /* binary output in host endianess */
    fprintf(stderr, "writing out result...\n");
    fwrite(kmers, sizeof *kmers, ilen, stdout);
#endif

    return 0;
}

バイナリファイル形式は、次のプログラムで人間が読める出力形式に変換できます。

#include <stdlib.h>
#include <stdio.h>

static int inbuf[BUFSIZ];
static char outbuf[BUFSIZ];

extern int main()
{
    int i, n, sum = 1;

    setbuf(stdout, outbuf);

    while ((n = fread(inbuf, sizeof *inbuf, BUFSIZ, stdin)) > 0)
        for (i = 0; i < n; i++)
            printf("%d\n", sum += inbuf[i] - 1);

    if (ferror(stdin)) {
        perror("Error reading input");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

出力例

ファイルのバイナリ形式での出力例をchr22.fa見つけることができるここにbzip2 -dまず解凍してください。人間が読める形式の出力よりもはるかに圧縮率が高い(3.5 kB対260 MB)ため、出力はバイナリ形式でのみ提供されます。ただし、圧縮されていないときの参照出力のサイズは924 MBであることに注意してください。次のようなパイプを使用したい場合があります。

bzip2 -dc chr2.kmerdiffdat.bz2 | ./unbin | less

これは非常に興味深いです。各部分の時間を分解できますか?suffix配列の作成は10%未満の時間のようです。

1
ステップ1には約3秒、ステップ2には約20秒、ステップ3には残りの時間がかかります。手順4にかかる時間はごくわずかです。
FUZxxl 2015

1
@FUZxxl最大p値の分布をプロットして、if p> k(k-merのみを数える)をカットすることでステップ3をどれだけ高速化できるかを確認しましたか?ただし、ステップ2に20秒かかる場合は、そこに十分なスペースがない可能性があります。素晴らしい説明btw
randomra

1
@Lembik使用しないでくださいcat。のようにシェルリダイレクトを使用し./dsskmer <~/Downloads/chr2.fsます。コードは、入力ファイルの長さを知る必要がありますが、パイプでは不可能です。
FUZxxl 2015

2
@Lembik私には遅すぎないようです。多分彼らはただの悪いプログラマーなのだろう。
FUZxxl 2015

7

C ++、k = 16、37秒

入力のすべての16 merを計算します。各16 merは、シンボルの4ビットを64ビットワードにパックします(1つのビットパターンはEOF用に予約されています)。次に、64ビットの単語が並べ替えられます。各kの答えは、並べ替えられた単語の4 * kの上位ビットがどのくらいの頻度で変化するかを調べることで読み取ることができます。

テスト入力には、約1.8GBを使用しています。

きっと読解速度が上がると思います。

出力:

14
92
520
2923
15714
71330
265861
890895
2482912
5509765
12324706
29759234
69001539
123930801
166196504
188354964

プログラム:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>

int main(int argc, char *argv[]) {
    // read file
    printf("reading\n");
    FILE *f = fopen(argv[1], "rb");
    fseek(f, 0, SEEK_END);
    int32_t size = ftell(f);
    printf("size: %d\n", size);
    fseek(f, 0, SEEK_SET);

    // table to convert 8-bit input into 4-bit tokens.  Reserve 15 for EOF.
    int ntokens = 0;
    int8_t squash[256];
    for(int i = 0; i < 256; i++) squash[i] = -1;

    uint64_t *buf = (uint64_t*)malloc(8*size);

    int32_t n = 0;
    uint64_t z = 0;
    for(int32_t i = 0; i < size; i++) {
        char c = fgetc(f);
        if(c == '\n') continue;
        int8_t s = squash[c];
        if(s == -1) {
            if(ntokens == 15) {
                printf("too many tokens\n");
                exit(1);
            }
            squash[c] = s = ntokens++;
        }
        z <<= 4;
        z += s;
        n++;

        if(n >= 16) buf[n-16] = z;
    }
    for(int32_t i = 1; i < 16; i++) {
        z <<= 4;
        z += 15;
        buf[n-16+i] = z;
    }
    printf("   n: %d\n", n);

    // sort these uint64_t's
    printf("sorting\n");
    std::sort(buf, buf+n);

    for(int32_t k = 1; k <= 16; k++) {
        // count unique entries
        int32_t shift = 64-4*k;
        int32_t cnt = 1;
        int64_t e = buf[0] >> shift;
        for(int32_t i = 1; i < n; i++) {
            int64_t v = buf[i] >> shift;
            if((v & 15) == 15) continue; // ignore EOF entries
            if(v != e) {
                cnt++;
                e = v;
            }
        }

        printf("%d\n", cnt);
    }
}

でコンパイルしg++ -O3 kmer.cc -o kmerて実行し./kmer chr2.faます。


出力フォーマットに従ってください。kしないでください。
FUZxxl 2015

それ以外の場合、これはクールなソリューションです。
FUZxxl 2015

@FUZxxl:修正されました。
Keith Randall

繰り返される部分文字列の最長の長さを見つけるのにどれくらい時間がかかると思いますか?私はあなたがそれより長いkのすべての答えを自動的に知っていると思っていました。

@Lembik:最長の繰り返し部分文字列を線形時間で実行できます。しかし、私はそれがあまり役に立たないと思います-サンプル入力には本当に長い繰り返しサブストリングがあり、少なくとも数千のシンボルがあります。
Keith Randall

4

C ++-FUZxxlソリューションに対する改善

私は計算方法自体にまったく信用を払う価値はありません。その間にもっと良いアプローチが現れない場合、報奨金はFUZxxlに正しく行くべきです。

#define _CRT_SECURE_NO_WARNINGS // a Microsoft thing about strcpy security issues
#include <vector>

#include <string>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstring>
using namespace std;

#include "divsufsort.h"

// graceful exit of sorts
void panic(const char * msg)
{
    cerr << msg;
    exit(0);
}

// approximative timing of various steps
struct tTimer {
    time_t begin;
    tTimer() { begin = time(NULL); }
    void print(const char * msg)
    {
        time_t now = time(NULL);
        cerr << msg << " in " << now - begin << "s\n";
        begin = now;
    }
};

// load input pattern
unsigned char * read_sequence (const char * filename, int& len)
{
    ifstream file(filename);
    if (!file) panic("could not open file");

    string str;
    std::string line;
    while (getline(file, line)) str += line;
    unsigned char * res = new unsigned char[str.length() + 1];
    len = str.length()+1;
    strcpy((char *)res, str.c_str());
    return res;
}

#ifdef FUZXXL_METHOD
/*
* Compute for all k the number of k-mers. kmers will contain at index i the
* number of (i + 1) mers. The count is computed as an array of differences,
* where kmers[i] == kmersum[i] - kmersum[i-1] + 1 and then summed up by the
* caller. This algorithm is a little bit unclear, but when you write subsequent
* suffixes of an array on a piece of paper, it's easy to see how and why it
* works.
*/
static void count(const unsigned char *buf, const int *sa, int *kmers, int n)
{
    int i, cl, cp;

    /* the first item needs special treatment */
    /*
        kuroi neko: since SA now includes the null string, kmers[0] is indeed 0 instead of 1
    */
//  kmers[0]++;

    for (i = 1; i < n; i++) {
        /* The longest common prefix of the two suffixes */
        cl = n - (sa[i - 1] > sa[i] ? sa[i - 1] : sa[i]);
#ifdef ICAP
        cl = (cl > ICAP ? ICAP : cl);
#endif
        for (cp = 0; cp < cl; cp++)
        if (buf[sa[i - 1] + cp] != buf[sa[i] + cp])
            break;

        /* add new prefixes to the table */
        kmers[cp]++;
    }
}

#else // Kasai et al. method

// compute kmer cumulative count using Kasai et al. LCP construction algorithm
void compute_kmer_cumulative_sums(const unsigned char * t, const int * sa, int * kmer, int len)
{
    // build inverse suffix array
    int * isa = new int[len];
    for (int i = 0; i != len; i++) isa[sa[i]] = i;

    // enumerate common prefix lengths
    memset(kmer, 0, len*sizeof(*kmer));
    int lcp = 0;
    int limit = len - 1;
    for (int i = 0; i != limit; i++)
    {
        int k = isa[i];
        int j = sa[k - 1];
        while (t[i + lcp] == t[j + lcp]) lcp++;

        // lcp now holds the kth longest commpn prefix length, which is just what we need to compute our kmer counts
        kmer[lcp]++;
        if (lcp > 0) lcp--;
    }
    delete[] isa;
}

#endif // FUZXXL_METHOD

int main (int argc, char * argv[])
{
    if (argc != 2) panic ("missing data file name");

    tTimer timer;
    int blen;
    unsigned char * sequence;
    sequence = read_sequence(argv[1], blen);
    timer.print("input read");

    vector<int>sa;
    sa.assign(blen, 0);
    if (divsufsort(sequence, &sa[0], blen) != 0) panic("divsufsort failed");
    timer.print("suffix table constructed");

    vector<int>kmers;
    kmers.assign(blen,0);

#ifdef FUZXXL_METHOD
    count(sequence, &sa[0], &kmers[0], blen);
    timer.print("FUZxxl count done");
#else
    compute_kmer_cumulative_sums(sequence, &sa[0], &kmers[0], blen);
    timer.print("Kasai  count done");
#endif

    /* sum up kmers differences */
    for (int i = 1; i < blen; i++) kmers[i] += kmers[i - 1] - 1;
    timer.print("sum done");

    /* human output */

    if (blen>10) blen = 10; // output limited to the first few values to avoid cluttering display or saturating disks

    for (int i = 0; i != blen; i++) printf("%d ", kmers[i]);
    return 0;
}

私は単に葛西らを使いましたO(n)でLCPを計算するアルゴリズム
残りはFUZxxlコードの単なる改作であり、あちこちでより簡潔なC ++機能を使用しています。

比較できるように、元の計算コードを残しました。

最も遅いプロセスはSA構築とLCPカウントであるため、無視できるほどのゲインでコードが乱雑にならないように、他の最適化のほとんどを削除しました。

長さ0のプレフィックスを含めるようにSAテーブルを拡張しました。これにより、LCP計算が容易になります。

私は長さ制限オプションを提供しませんでした。最も遅いプロセスは、SA計算であり、サイズを小さくすることができません(または、少なくともそれがどのようになるかわかりません)。

また、バイナリ出力オプションを削除し、表示を最初の10値に制限しました。
このコードは単なる概念実証であるため、ディスプレイを乱雑にしたり、ディスクを飽和させたりする必要はありません。

実行可能ファイルを構築する

Win32 2Gb割り当ての制限を克服するには、x64用にプロジェクト全体(Liteバージョンのをdivsufsort含む)をコンパイルする必要がありました。

divsufsortintsの代わりにsize_ts を頻繁に使用するため、コードは一連の警告をスローしますが、2Gb未満の入力では問題になりません(とにかく26GbのRAMが必要です:D)。

Linuxビルド

コマンドをコンパイルmain.cppしてdivsufsort.c使用します。

g++ -c -O3 -fomit-frame-pointer divsufsort.c 
g++ -O3 main.cpp -o kmers divsufsort.o

divsufsort3Gbより少し多く割り当てることができる限り、通常のライブラリはネイティブLinuxで正常に動作するはずです。

公演

Kasaiアルゴリズムには、逆SAテーブルが必要です。これは、1文字あたりさらに4バイトを消費し、合計で13バイトになります(FUZxxlメソッドでは9バイトではありません)。

したがって、リファレンス入力のメモリ消費量は3Gbを超えます。

一方、計算時間は劇的に改善され、アルゴリズム全体がO(n)になりました。

input read in 5s
suffix table constructed in 37s
FUZxxl count done in 389s
Kasai  count done in 27s
14 92 520 2923 15714 71330 265861 890895 2482912 5509765 (etc.)

さらなる改善

SAの構築は現在最も遅いプロセスです。
いくつかのビットdivsufsortアルゴリズムは、私には、コンパイラ、未知のどんな組み込み機能で並列化されることを意図しているが、必要に応じてコードが(もっと古典的なマルチスレッド化に適応するために簡単なはずラà例えばC ++ 11、)。
libには、さまざまなバケットサイズや入力文字列内の個別のシンボルの数など、パラメーターのトラックロードもあります。私はそれらをざっと見ただけでしたが、文字列が無限のACTG順列である場合(そしてパフォーマンスに必死である場合)、アルファベットの圧縮は試してみる価値がある思います。

そこあまりにSAからLCPを計算するためのいくつかの並列化の方法が存在するが、コードは少し私のちっぽけなのi3-2100@3.1GHzより強力なプロセッサ上で1分の下で実行する必要があるため、全体のアルゴリズムはO(n)であり、私はこれを疑います努力する価値があります。


2
クレジットの期日までにクレジットを付与する場合は+1)(そして素晴らしいスピードアップ!)
マーティンエンダー

1
これはすごいです。素晴らしい改善。
FUZxxl 2015

LCPアレイについてさえ知りませんでした。葛西ほか アルゴリズムも本当に素晴らしいです。それはそんなに多くのメモリを必要とすると少し言われました。
FUZxxl 2015

@FUZxxlああ、それは常に同じ古い速度/メモリのトレードオフですが、45%メンバーだと思います。コストの増加は、O(n)の複雑さに到達するための許容可能な価格です。

2
@kuroineko LCP配列を使用せずに必要なデータを線形時間で構築する方法を考えています。チェックさせて…
FUZxxl

1

C(私のマシンでは1分で最大10まで解くことができます)

これは非常にシンプルなソリューションです。見つかったk -merのツリーを作成し、カウントします。メモリを節約するために、文字は最初に0からn -1 までの整数に変換されます。ここで、nは入力内の異なる文字の数であるため、決して表示されない文字用のスペースを用意する必要はありません。さらに、追加のメモリを節約するために、他のノードよりもリーフに割り当てられるメモリが少なくなります。このソリューションでは、私のマシンでの実行時に約200 MiBのRAMを使用します。私はまだそれを改善しているので、おそらく反復ではさらに速くなるかもしれません!

コンパイルするには、以下のコードをという名前のファイルに保存kmers.cし、POSIXのようなオペレーティングシステムで実行します。

cc -O -o kmers kmers.c

コンパイラがサポートしている場合は、代わりに-O3使用-Oすることをお勧めします。実行するには、まず解凍chr2.fa.gzchr2.faてから実行します。

./kmers <chr2.fa

これにより、出力が段階的に生成されます。時間とスペースの両方を制限したい場合があります。のようなものを使う

( ulimit -t 60 -v 2000000 ; ./kmers <chrs.fa )

必要に応じてリソースを削減します。

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
 * A tree for the k-mers. It contains k layers where each layer is an
 * array of struct tr. The last layer contains a 1 for every kmer found,
 * the others pointers to the next layer or NULL.
 */
struct tr {
    struct tr *n;
};

/* a struct tr to point to */
static struct tr token;

static void     *mem(size_t s);
static int       add(struct tr*, unsigned char*, int, size_t);
static unsigned char    *input(size_t*);
extern int       main(void);

/*
 * Allocate memory, fail if we can't.
 */
static void*
mem(s)
    size_t s;
{
    void *p;

    p = calloc(s, 1);
    if (p != NULL)
        return p;

    perror("Cannot malloc");
    abort();
}

/*
 * add s to b, return 1 if added, 0 if present before. Assume that n - 1 layers
 * of struct tr already exist and only add an n-th layer if needed. In the n-th
 * layer, a NULL pointer denotes non-existance, while a pointer to token denotes
 * existance.
 */
static int
add(b, s, n, is)
    struct tr *b;
    unsigned char *s;
    size_t is;
{
    struct tr **nb;
    int added;
    int i;

    for (i = 0; i < n - 2; i++) {
        b = b[s[i]].n;
    }

    nb = &b[s[n - 2]].n;

    if (*nb == NULL || *nb == &token)
        *nb = mem(is * sizeof *b);

    added = (*nb)[s[n - 1]].n == NULL;
    (*nb)[s[n - 1]].n = &token;

    return (added);
}

/*
 * load input from stdin and remove newlines. Save length in flen.
 */
static unsigned char*
input(flen)
    size_t *flen;
{
    size_t n, len, pos, i, j;
    unsigned char *buf;

    if (fseek(stdin, 0L, SEEK_END) != 0) {
        perror("Cannot seek stdin");
        abort();
    }

    len = ftello(stdin);
    if (len == -1) {
        perror("Cannot tell stdin");
        abort();
    }

    rewind(stdin);

    /* no need to zero out, so no mem() */
    buf = malloc(len);
    if (buf == NULL) {
        perror("Cannot malloc");
        abort();
    }

    pos = 0;

    while ((n = fread(buf + pos, 1, len - pos, stdin)) != 0)
        pos += n;

    if (ferror(stdin)) {
        perror("Cannot read from stdin");
        abort();
    }

    /* remove newlines */
    for (i = j = 0; i < len; i++)
        if (buf[i] != '\n')
            buf[j++] = buf[i];

    *flen = j;
    return buf;
}

extern int
main()
{
    struct tr *b;
    size_t flen, c, i, k, is;
    unsigned char *buf, itab[1 << CHAR_BIT];

    buf = input(&flen);

    memset(itab, 0, sizeof itab);

    /* process 1-mers */
    for (i = 0; i < flen; i++)
        itab[buf[i]] = 1;

    is = 0;
    for (i = 0; i < sizeof itab / sizeof *itab; i++)
        if (itab[i] != 0)
            itab[i] = is++;

    printf("%zd\n", is);

    /* translate characters into indices */
    for (i = 0; i < flen; i++)
        buf[i] = itab[buf[i]];

    b = mem(is * sizeof *b);

    /* process remaining k-mers */
    for (k = 2; k < flen; k++) {
        c = 0;

        for (i = 0; i < flen - k + 1; i++)
            c += add(b, buf + i, k, is);

        printf("%zd\n", c);
    }

    return 0;
}

改善点

  1. 8→9:最初にファイル全体を読み取り、1度前処理してコアに保存します。これにより、スループットが大幅に向上します。
  2. メモリ使用量を減らし、正しい出力を書き込んでください。
  3. 出力形式を再度修正します。
  4. 1つずつ修正します。
  5. 9→10:すでにやったことを捨てないでください。

出力は、実際には1行あたり1つの数値になります。現在、入力ファイルから文字列を出力しているようです。

@レンビックああ、なるほど!出力フォーマットを誤解しているようです。少し時間をかけて修正してください。
FUZxxl 2015

@Lembik出力形式が再度修正されました。必要に応じて、1分後にプログラムを強制終了するコードを追加できます。
FUZxxl 2015

ありがとう。あなたは現在の勝者です:) timeout 60s私にとっては問題なく動作するので、1分後にコードを強制終了する方法を構築する必要はありません。

も使用できますulimit -t 60
FUZxxl 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.