Skip to content

Deepcopy implementation #23460

Open
Open
@Alogani

Description

@Alogani

Summary

Hello,

I propose a deepcopy implementation using a fieldPairs iterators. It seems to work for all types and is 3 times quicker than the actual one. It has not been tested on cyclic data structures.

Here is the code below :

proc deepCopyImpl[T](dest: var T; src: T) =
    when typeof(src) is ref or typeof(src) is ptr:
        if src != nil:
            dest = T()
            dest[] = src[]
            deepCopyImpl(dest[], src[])
    elif typeof(src) is object:
        for _, v1, v2 in fieldPairs(dest, src):
            deepCopyImpl(v1, v2)

proc deepCopy*[T](dest: var T; src: T) =
    ## This procedure copies values and create new object for references
    ## Also copies pointers, so unmanaged memory is unsafe if pointer is not wrapped into an object with a destructor
    # Should behave exactly like system.deepCopy
    dest = src
    deepCopyImpl(dest, src)

Here is also the code for a deepEqual checking implementation :

func deepEqual*[T](a, b: T): bool =
    ## Check values recursively
    ## Ignore References/adresse inequality
    result = true
    when typeof(a) is ref or typeof(a) is ptr:
        if a != nil and b != nil:
            result = deepEqual(a[], b[])
        else:
            result = a == b
        if result == false:
            return
    elif typeof(a) is object:
        for name, v1, v2 in fieldPairs(a, b):
            result = deepEqual(v1, v2)
            if result == false:
                return
    else:
        result = a == b

Here are some unittests :

import mylib/Testing
testing():
    import strtabs, std/with

    type
        MyProc = ref object
            pid: int
            alive: bool
            data: string

        MyTerminal = object
            size: int
            data: string
            process: MyProc

        ShellOptions = enum
            QuoteArgs, MergeStderr

        Shell = ref object
            cmd: string
            args: seq[string]
            options: set[ShellOptions]
            env: StringTableRef
            process: MyProc
            terminal: MyTerminal
            nilval1: pointer
            nilval2: MyProc

    var s1 = Shell()
    with s1:
        cmd = "ls"
        args = @["-l", "-a"]
        options = {MergeStderr}
        env = newStringTable()
        process = MyProc()
        with process:
            pid = 10
            alive = true
            data = "This is running"
        with terminal:
            size = 1000
            data = "youpi"
            process = MyProc()
            with process:
                pid = 9
                alive = true
                data = "Have a terminal"
    s1.env["SHELL"] = "/bin/bash"
    
    proc assertions()
    var s2: Shell
    
    ## System.deepCopy is commented
    #[
        runTest("system.deepCopy"):
            s2 = Shell()
            system.deepCopy(s2, s1)
            assertions()
        runBench("system.deepCopy", 1):
            s2 = Shell()
            system.deepCopy(s2, s1)
    ]#
    
    runTest("proposal.deepCopy"):
        s2 = Shell()
        deepCopy(s2, s1)
        assertions()
    runBench("proposal.deepCopy", 1):
        s2 = Shell()
        deepCopy(s2, s1)

    
    proc assertions() =
        doAssert deepEqual(s2, s1), "Expected s1 deeply equal s2"
        doAssert deepEqual(s1, s2), "Expected s2 deeply equal s1"
        doAssert s2.cmd == s1.cmd, "Field must be same"
        doAssert s2.args == s1.args, "Field must be same"
        doAssert s2.options == s1.options, "Field must be same"
        doAssert $(s2.env) == $(s1.env), "Field must be same"
        doAssert s2.process.pid == s1.process.pid, "Field must be same"
        doAssert s2.terminal.process.pid == s1.terminal.process.pid, "Field must be same"
        doAssert s2.nilval1 == s1.nilval1, "Undefined ref must be nil"
        doAssert s2.nilval2 == s1.nilval2, "Undefined ref must be nil"


        # Inequality checks
        doAssert s2 != s1, "Refs must be different"
        doAssert s2.terminal != s1.terminal, "Must be different because one field is ref"
        doAssert s2.terminal.process != s1.terminal.process, "Refs must be different"
        
        s2.terminal.process.pid = 10
        doAssert not deepEqual(s2, s1), "Expected s1 no longer equal s2"

        s2.cmd = "changed"
        doAssert s2.cmd != s1.cmd, "Field must not be same"
        s2.args.add("myfile")
        doAssert s2.args != s1.args, "Field must not be same"
        s2.options.incl(QuoteArgs)
        doAssert s2.options != s1.options, "Field must not be same"
        s2.env["field"] = "changed"
        doAssert $(s2.env) != $(s1.env), "Field must not be same"
        s2.process.pid = 53
        doAssert s2.process.pid != s1.process.pid, "Field not must be same"
        s2.terminal.size = 30
        doAssert s2.terminal != s1.terminal, "Field must not be same"
        s2.terminal.process.pid = 87
        doAssert s2.terminal.process.pid != s1.terminal.process.pid, "Field must not be same"

Description

The actual deepcopy implementation is slow and require a supplementary compile flag.

Alternatives

No response

Examples

No response

Backwards Compatibility

No response

Links

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions