Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket Cache - RFC #128

Closed
ly29 opened this issue May 2, 2014 · 15 comments
Closed

Socket Cache - RFC #128

ly29 opened this issue May 2, 2014 · 15 comments

Comments

@ly29
Copy link
Collaborator

ly29 commented May 2, 2014

History

All sockets had a String Property that was used to store the socket values.

self.outputs['Matrix'].MatrixProperty = str(matrixes)
And then they where read like this.
Integer = eval(self.inputs['Integer'].links[0].from_socket.StringsProperty)[0][0]

This was a simple good solution but obviously the biggest performance problem in Sverchok.

Performance testing from February:
diagram3-2

  • Today, using string and eval
  • Step 1: Using socket cache but still writing the string properties but only reading them when needed. A step in the conversion
  • Step 2: Initial implementation using python built in deepcopy
  • Step 2, New: Using custom made sv_deep_copy (see below)
  • Step 3, Using simple copy and not deep copy.

"step 2 new" is where we are today.

Current implementation

def SvGetSocketAnyType(self, socket):

def SvSetSocketAnyType(self, socket_name, out):

Issues:

  • Inconsistency with parameters
  • Long names
  • Socket names are not unique
    Note: This is because these functions are used in a way they were originally not planned to be used.

If we look at these function we see that already today they are only wrappers for another interface.
def SvSetSocket(socket, out):
def SvGetSocket(socket, copy = False):

These functions perform the actual Socket Cache functionality. data=socket_data_cache[ng][s_id]
They maintain a socket cache for each nodegroup that is reset on each Edit event, see #121 .
Note This reset happens in the makeTreeUpdate2 function.
Still these names are longer than needed and before defining the new interface I think we should discuss it.

Data integrity.

Data is only stored for each output socket. But since this data can 1) be read many times by the same node using partial updates and 2) be read from many sockets. It needs to be ensured that it is not modified. Many nodes always create new data but some modify the input in place. To ensure safety and to be able to keep the nodes as black boxes the following is used.

def sv_deep_copy(lst):
    if isinstance(lst,(list,tuple)):
        if lst and not isinstance(lst[0],(list,tuple)):
            return lst[:]
        return [sv_deep_copy(l) for l in lst]
    return lst

This much quicker than the built in deepcopy function that is very advanced and can handle many scenarios. If you can make this function quicker everything in Sverchok goes quicker. Using this is instead of copy.deepcopy() is the only difference between "step 2" and "step 2 new"

This function parses the data on every get socket unless the copy parameter is set to True when it uses a shallow copy.

Future

Interface

I think the best idea is to implement socket data cache function on in the socket class.

So we can use socket.get() and socket.set(data)

We could remove offload a lot of code for the nodes by providing an argument to be used instead if the socket isn't linked. int=socket.get([[self.integer]])

An optional argument for shallow copy for nodes that do not modify data in place, that would be off by default since many strange things can start to happen then...
Moving to the socket interface to the socket class could also provide a simple way to make specialized interfaces for different socket types in the future.
Other ideas:

Cache datum

To provide an integer for the last modified input so nodes can see which input has changed.
The integer would be globally updated and always increasing. However I am not sure if this is worth the effort right now.

Questions?

@ly29
Copy link
Collaborator Author

ly29 commented May 2, 2014

Also note that setting a output socket in no way trigger any update events, that is done by the update system.

@ly29 ly29 mentioned this issue May 2, 2014
18 tasks
@nortikin
Copy link
Owner

nortikin commented May 2, 2014

As usual, i have to reread several times and try to understand to start ask questions.
Thanks for explanation

@ly29
Copy link
Collaborator Author

ly29 commented May 2, 2014

The test setup I used. Tests mostly done with N=500
test-setup

@ly29
Copy link
Collaborator Author

ly29 commented May 7, 2014

Idea for socket code. Now the we started packing the name of the bpy prop we could simplify the code by packing the prop value to the right level.

socket=self.inputs['Radius']
radius=socket.get()

Where it would return the input list or [[self.radius]] packed into a list already removing the need for code like.

        if 'Radius' in self.inputs and self.inputs['Radius'].links:
            Radius = SvGetSocketAnyType(self,self.inputs['Radius'])[0]
        else:
            Radius = [self.rad_]

could become

        if 'Radius' in self.inputs:
            Radius = self.inputs['Radius'].get()[0]

And that `if 'Radius' in self.inputs:`` I want to care of by separating ui and process code. So that the end could to this instead.

Radius=self.inputs['Radius'].get()[0]

@nortikin
Copy link
Owner

nortikin commented May 7, 2014

how did you extend socket class with get function?

@ly29
Copy link
Collaborator Author

ly29 commented May 7, 2014

Well, I didn't look at the class reference when I wrote that so get is bad idea since it already exists in the socket class. I did a version that I have not tested yet to use for with StringsSocket

        def sv_get(self,default=None):
            if self.links and not self.is_output:
                return SvGetSocket(self)
            elif prop_name:
                return [[self.node[self.prop_name]]]
            else:
                return default

@ly29
Copy link
Collaborator Author

ly29 commented May 7, 2014

But to write a method the for the sockets you can just write in node_s in the respective classes.

@nortikin
Copy link
Owner

nortikin commented May 7, 2014

http://www.blender.org/documentation/blender_python_api_2_70a_release/bpy.types.NodeSocket.html?highlight=socket#bpy.types.NodeSocket
don't see .get()
Sorry for often questions, but i have to understand this magic. When we started, there was no wiki for custom nodes.
You right, we can write own get function easily.

@ly29
Copy link
Collaborator Author

ly29 commented May 7, 2014

Look under Inherited Functions

Don't hesitate to ask.

@nortikin
Copy link
Owner

nortikin commented May 7, 2014

thanks, i'm blind OMG

@ly29
Copy link
Collaborator Author

ly29 commented May 11, 2014

One of my early goals of the socket cache was to stop destroying data and to allow more complex data to exist between nodes.
I also spent some time thinking about a mesh socket type which would transport bmeshes with the output given by each bmesh op, you could do the intersection separate from bisect etc.
I think the right place to implement this is the socket interface.
I think we can keep SvGetSocketAnyType as legacy interface, that works like today, and use a new interface were you get Matrix object and Vector objects directly from the socket, and in the future other datatypes.
I propose the following interface where both getting by name and socket works. I also propose that we specify a method for setting a default value, which can easily be packed into input stream.

def sv_get(obj,name=''):
   if isinstance(obj,NodeSocket):
       return SvGetSocket(socket)
   elif isinstance(obj,Node):
       return SvGetSocket(obj.inputs[name])
   else:
       Error...

We modify SvGetSocketAnytype to destroy data when need and we create data when needed for the new interface. Thus keeping compatibility.

@ly29
Copy link
Collaborator Author

ly29 commented May 11, 2014

vectors

>>> n.inputs['Y'].sv_get()
[[1.0]]

>>> n.inputs['X'].sv_get()
[[-0.4099999964237213, -0.25333333015441895, -0.09666666388511658, 0.06000000238418579, 0.21666666865348816, 0.3733333349227905, 0.5300000011920929, 0.6866666674613953, 0.8433333337306976, 1.0]]

@ly29
Copy link
Collaborator Author

ly29 commented May 12, 2014

@zeffii
sv_get and sv_set is only for stringssocket right now because for the other socket types I want try to preserve data with the new interface, for that I have code more for the backend. I think exposing the socket cache interface however should be done quickly for debug reasons if nothing else.
Perhaps on tree level so it is easily available in blender console.

ng=D.node_groups['NodeTree']
ng.sv_get(socket)
ng.sv_get(node_name,socket_identifier)

@ly29
Copy link
Collaborator Author

ly29 commented May 12, 2014

Also added sv_get to all sockets so one can inspect socket data from python console. setting it and doing partial updates can have fun effects... Considered beta and unstable except for stringsocket.

@ly29
Copy link
Collaborator Author

ly29 commented Jun 28, 2014

sv_get and sv_set has gotten deepcopy and works for all socket types as SvGetSocketAnyType does, properly with input/output.

  • Doesn't support automatic creation of matrix and vector, not sure if it should either for now.
  • Doesn't work with prop_name

@ly29 ly29 closed this as completed Jun 28, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants