代码之家  ›  专栏  ›  技术社区  ›  Ilhan

为什么将这个代码段从C#翻译成C++会降低性能?

  •  0
  • Ilhan  · 技术社区  · 9 年前

    我已将问题缩小到以下片段:

       public class SuffixTree
        {
            public class Node
            {
                public int Index = -1;
                public Dictionary<char, Node> Children = new Dictionary<char, Node>();
            }
    
            public Node Root = new Node();
            public String Text;
    
            public SuffixTree(string s)
            {
                Text = s;
                for (var i = s.Length - 1; i >= 0; --i)
                    InsertSuffix(s, i);
            }
    
            public void InsertSuffix(string s, int from)
            {
                var cur = Root;
                for (int i = from; i < s.Length; ++i)
                {
                    var c = s[i];
                    if (!cur.Children.ContainsKey(c))
                    {
                        var n = new Node() { Index = from };
                        cur.Children.Add(c, n);
    
                        return;
                    }
                    cur = cur.Children[c];
                }
            }
    
            public bool Contains(string s)
            {
                return FindNode(s) != null;
            }
    
            private Node FindNode(string s)
            {
                var cur = Root;
                for (int i = 0; i < s.Length; ++i)
                {
                    var c = s[i];
                    if (!cur.Children.ContainsKey(c))
                    {
                        for (var j = i; j < s.Length; ++j)
                            if (Text[cur.Index + j] != s[j])
                                return null;
                        return cur;
                    }
                    cur = cur.Children[c];
                }
                return cur;
            }
        }
    }
    

    C++

    struct node
    {
        int index;
        std::unordered_map<char, node*> children;
    
        node() { this->index = -1; }
        node(int idx) { this->index = idx; }
    };
    
    struct suffixTree
    {
        node* root;
        char* text;
    
        suffixTree(char* str)
        {
            int len = strlen(str) + 1;
            this->text = new char[len];
            strncpy(this->text, str, len);
    
            root = new node();
            for (int i = len - 2; i >= 0; --i)
                insertSuffix(str, i);
        }
    
        void insertSuffix(char* str, int from)
        {
            node* current = root;
            for (int i = from; i < strlen(str); ++i)
            {
                char key = str[i];
                if (current->children.find(key) == current->children.end())
                {
                    current->children[key] = new node(from);
                    return;
                }
                current = current->children[key];
            }
        }
    
        bool contains(char* str)
        {
            node* current = this->root;
            for (int i = 0; i < strlen(str); ++i)
            {
                char key = str[i];
                if (current->children.find(key) == current->children.end())
                {
                    for (int j = i; j < strlen(str); ++j)
                        if (this->text[current->index + j] != str[j])
                            return false;
                    return true;
                }
                current = current->children[key];
            }
        }
    }
    

    在这两种情况下,我都创建了一个后缀树,然后在一个更大的函数中使用它,该函数与post无关(我们称之为F())。我在两个随机生成的长度为100000的字符串上进行了测试。C版本构建了我的后缀树,并在F()中使用它,总执行时间为: 而我“翻译成C++的”代码在

    我进一步深入研究了这一点,似乎在我的C++代码中,构造函数需要 当使用F()中的树时,在 48毫秒

    结论

    看来主要问题在于 insertSuffix() ,也许是我对 结构有人能解释一下吗?我是不是在C++变体中犯了一些新手错误,导致对象构造花费了这么长时间?

    我已经编译了C#和C++程序以获得最高速度/氧气(release)

    1 回复  |  直到 9 年前
        1
  •  6
  •   Daniel H    9 年前

    System.String 包括its Length ,因此可以在恒定时间内获得长度。在C++中,a std::string 还包括其 size

    但是,您没有使用C++ (为了更好地翻译算法,您应该这样做);你正在使用 C-style null-terminated char array . 那个 char* 烧焦 strlen 函数查看每个 烧焦 从指向前的一个,直到找到一个空字符 '\0' (不要与 null pointer ); 这是很昂贵的,你在循环的每次迭代中都会这样做 insertSuffix

    在使用C++时,如果您发现自己在使用原始指针(任何涉及 * struct node 和 node* root . 两者都使用 node 节点 一些 无限大 ,但这是由 std::unordered_map ).


    其他几点提示:

    • initialization lists .
    • 插入后缀 采取行动 作为第一个参数,将其设置为 std::string const& contains 应该采取 标准::字符串常量(& 插入后缀 可以看到 text from .
    • C++支持 foreach-like construct ,您可能更喜欢它 for
    • 如果您使用的是C++的最新版本,即C++17,在技术上还没有定稿,但已经足够接近了,那么您应该使用 std::string_view 标准::字符串 包含 文本 成员,甚至对于构造函数;它在 文本 成员本身,因为正在查看的对象可能是临时的。然而,在C++中,生命周期有时可能很复杂,在掌握诀窍之前,你可能只想使用它 为了安全起见。
    • 自从 在概念之外没有用处 suffixTree ,它可能应该在它里面,就像在C#版本中一样。作为与C#版本的一个偏差,您可能需要将 和数据成员 root 文本 private public 成员。