I am working on a Python C-extension to calculate Damerau-Levenshtein distance. I am not really familiar with C at all--I just know that it generally has better performance. However, I am not sure how to bring about these performance gains. I translated a Python version from https://github.com/jamesturk/jellyfish/blob/main/jellyfish/_jellyfish.py basically 1:1, and the speed is fine, but I suspect I am making performance concessions an experienced C programmer would not. I would be grateful if anyone could point out anything inefficient I am doing! Also, I am just worried dealing with ASCII characters.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string.h>
// This is a 1:1 translation of https://github.com/jamesturk/jellyfish/blob/main/jellyfish/_jellyfish.py
int min_int(int arr[], size_t size)
{
// simply loop over an array of integers to find the min
int min = arr[0];
for (size_t i = 1; i < size; i++)
{
int val = arr[i];
if (val < min)
{
min = val;
}
}
return min;
}
static PyObject* distance(PyObject *self, PyObject *args)
{
const char* s1;
const char* s2;
if ( !PyArg_ParseTuple(args, "ss", &s1, &s2) ) // s means string argument
{
return NULL;
}
int len1 = strlen(s1);
int len2 = strlen(s2);
int infinite = len1 + len2;
int nrows = len1 + 2;
int ncols = len2 + 2;
// initialize distance matrix
int score[nrows][ncols];
for (size_t i = 0; i < nrows; i++)
{
for (size_t j = 0; j < ncols; j++)
{
score[i][j] = 0;
}
}
score[0][0] = infinite;
for (size_t i = 0; i <= len1; i++)
{
score[i + 1][0] = infinite;
score[i + 1][1] = i;
}
for (size_t i = 0; i <= len2; i++)
{
score[0][i + 1] = infinite;
score[1][i + 1] = i;
}
// Since we are only dealing with ascii characters, this is equivalent to the dictionary
// in the Python implementation. Instead of accessing using the character as the index, we
// can cast the character to its integer version, and access by index.
int da[256] = { 0 };
for (size_t i = 1; i <= len1; i++)
{
int db = 0;
for (size_t j = 1; j <= len2; j++)
{
const char s2_char = s2[j - 1];
int i1 = da[(int)s2_char];
int j1 = db;
int cost = 1;
if (s1[i - 1] == s2_char)
{
cost = 0;
db = j;
}
int arr[4] = {
score[i][j] + cost,
score[i + 1][j] + 1,
score[i][j + 1] + 1,
score[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1)
};
score[i + 1][j + 1] = min_int(arr, 4);
}
const char s1_char = s1[i - 1];
da[(int)s1_char] = i;
}
long distance = score[len1 + 1][len2 + 1];
return PyLong_FromLong(distance);
}
The compiler flags are -Wsign-compare -Wunreachable-code -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk
It's available to view and play with on Godbolt. I am compiling on MacOS x86_64-apple-darwin21.6.0, clang version 14.