3
\$\begingroup\$

I have a 2d array of m * n dimension (m and n can vary from 1 to 100000). The following snippet of code checks if the sequence exists in the row and stores the index if it exists. The time taken by the following code on 10000*10000 matrix is 650 milliseconds. The sequential version of the code also takes the same time.

This on Intel Core i7-7560U CPU @ 2.40GHz × 4

#include <iostream>
#include <vector>
#include <future>

std::vector<int> lps;

void ComputeLPSArray(std::vector<int> const &pattern) {

    std::vector<int> lps(pattern.size());
    int len = 0;

    lps[0] = 0;

    int i = 1;
    while (i < (int) pattern.size()) {
        if (pattern[i] == pattern[len]) {
            len++;
            lps[i] = len;
            i++;
        } else {
            if (len != 0) {
                len = lps[len - 1];
            } else {
                lps[i] = 0;
                i++;
            }
        }
    }
}


int SearchPattern(std::vector<int> const &pattern, std::vector<int> const &row) {
    auto M = (int) pattern.size();
    auto N = (int) row.size();

    int i = 0;
    int j = 0;
    while (i < N) {

        if (pattern[j] == row[i]) {
            j++;
            i++;
        }
        if (j == M) {
            return 1;
        } else if (i < N && pattern[j] != row[i]) {
            if (j != 0)
                j = lps[j - 1];
            else
                i = i + 1;
        }
    }
    return -1;
}

std::vector<int> SearchSequence(std::vector<std::vector<int>> const &matrix, std::vector<int> const &sequence) {

    ComputeLPSArray(sequence);
    std::vector<int> result(matrix.size());
    unsigned int length = 0;
    std::vector<std::future<int>> f(matrix.size());

    for (unsigned int i = 0; i < matrix.size(); i++) {
        std::vector<int> row = matrix[i];
        f[i] = async(std::launch::async, [sequence, row] { return SearchPattern(sequence, row); });
    }

    for (unsigned int i = 0; i < f.size(); ++i) {
        if (f[i].get() == 1)
            result[length++] = i;
    }

    result.resize(length);

    return result;
}

int main() {

    int m = 25, n = 20; // assume `m` and `n` can vary.
    std::vector<std::vector<int >> matrix(m, std::vector<int>(n));

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            matrix[i][j] = rand() % 1000;
        }

    }

    std::vector<int> sequence = matrix[24]; // This is purely for testing purpose, actual input(matrix&sequence) is read from files.

    std::vector<int> result;
    result = SearchSequence(matrix, sequence); // This is the ONLY function which needs to be optimised.

    return 0;
}

How do I speed up the search time in this scenario?

\$\endgroup\$
17
  • \$\begingroup\$ what does the matrix store? any int up to about 2^32? \$\endgroup\$
    – juvian
    Sep 28, 2018 at 15:13
  • \$\begingroup\$ @juvian, Yes, The matrix contains integers. \$\endgroup\$
    – user180762
    Sep 28, 2018 at 15:19
  • \$\begingroup\$ Do you know any context of where is this used? Is it likely for the sequence to be present in many rows? You could keep a structure like hashmap or set to know which ints are in each row and avoid doing KMPSearchPattern on rows that do not have one of the ints in the sequence \$\endgroup\$
    – juvian
    Sep 28, 2018 at 15:24
  • \$\begingroup\$ @juvian, Yes, It is likely that sequence to be present in many rows. I have tried the hashmap version also instead of KMP but that didn't help much. I am looking for optimisation in data designing point of view than algorithmic point of view. \$\endgroup\$
    – user180762
    Sep 28, 2018 at 15:26
  • 3
    \$\begingroup\$ I don't suppose you could post a new version of the code that doesn't (A) require 10,000 user threads, nor (B) segfault even with m=10 and n=10? It's hard to compare apples and apples when the code posted doesn't run (and in fact crashes) on most normal computers. But certainly it'll run faster if you pass/capture more things by reference instead of by value. You're copying the world at least twice. \$\endgroup\$ Sep 28, 2018 at 23:10

1 Answer 1

2
\$\begingroup\$

I rewrote your SearchSequence function to be as absolutely brain-dead as possible:

#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>

std::vector<int> SearchSequence(std::vector<std::vector<int>> const &matrix, std::vector<int> const &sequence)
{
    std::vector<int> result;

    for (unsigned int i = 0; i < matrix.size(); i++) {
        auto it = std::search(matrix[i].begin(), matrix[i].end(), sequence.begin(), sequence.end());
        if (it != matrix[i].end()) {
            result.emplace_back(i);
        }
    }
    return result;
}

int main() {

    int m = 25000, n = 25000; // assume `m` and `n` can vary.
    std::vector<std::vector<int >> matrix(m, std::vector<int>(n));

    auto start_time = std::chrono::steady_clock::now();

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            matrix[i][j] = rand() % 1000;
        }
    }

    std::vector<int> sequence = matrix[9999]; // This is purely for testing purpose, actual input(matrix&sequence) is read from files.

    auto initial_time = std::chrono::steady_clock::now();
    std::cout
        << "Initializing the array with rand() took: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(initial_time - start_time).count() << " ms\n";

    std::vector<int> result = SearchSequence(matrix, sequence); // This is the ONLY function which needs to be optimised.

    auto search_time = std::chrono::steady_clock::now();
    std::cout
        << "Executing the actual search took: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(search_time - initial_time).count() << " ms\n";

    return 0;
}

On my laptop, this prints:

$ clang++ -std=c++14 -O3 ./x.cc
$ ./a.out
Initializing the array with rand() took: 5304 ms
Executing the actual search took: 2 ms

$ clang++ -std=c++14 -O0 ./x.cc
$ ./a.out
Initializing the array with rand() took: 7670 ms
Executing the actual search took: 8 ms

This is single-threaded, with absolutely no cleverness to the search at all — I'm just using the $O(n^2)$ std::search algorithm from the standard library.

I suggest that your business with std::vector<std::future<int>> is completely unnecessary, and that if you're measuring any slowness, it's slowness in initializing the matrix with random numbers.

\$\endgroup\$
8
  • \$\begingroup\$ The complexity of std::search is linear in count1*count2 (where countX is the distance between firstX and lastX). so, theoretically what I implemented must be better than std::search. And the real question is how can I reduce the actual search time? Why threads aren't really helping in this scenario? Also, the time for initialising array isn't a concern here because I am reading the large array from file. \$\endgroup\$
    – user180762
    Oct 1, 2018 at 11:45
  • 1
    \$\begingroup\$ Yes, but when you say your code is slow, you mean your code for initializing the array is slow! The actual search, on a 25000 x 25000 array, is so fast as to be almost impossible to measure. Adding the overhead of reading from disk, or thread spawning and synchronization, or anything else, is just going to slow you down, in this case. \$\endgroup\$ Oct 1, 2018 at 17:05
  • \$\begingroup\$ "theoretically what I implemented must be better than std::search": Yes! Modulo bugs, theoretically what you implemented is better than std::search. But in practice what you implemented is much worse than std::search. You forgot to account for the constant factor: spawning a single thread is a complex operation that might involve the equivalent of several 25000-element-array-traversals just in order to create the new thread and all its bookkeeping. \$\endgroup\$ Oct 1, 2018 at 17:09
  • 1
    \$\begingroup\$ @AdithyaPotlapelli Isn't KMP meant to improve the worst case of searching (many repeated elements in the pattern and sequence)? I'm not sure what your actual data looks like, but I suspect random integers are unlikely to show any benefit from using KMP over the normal std::search algorithm. \$\endgroup\$
    – user673679
    Oct 1, 2018 at 17:20
  • \$\begingroup\$ @user673679 I've taken that into consideration. I have repeated elements in the pattern and sequence. So I thought KMP might have an edge over std::search. \$\endgroup\$
    – user180762
    Oct 2, 2018 at 9:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.