element.py: Extract __extrace_sandbox_config to a cython module

__extract_sandbox_config takes roughly 1.5% of our runtime in our benchmarks.

Moving it to cython allows us to regain part of this time and speeds
up all buildstream's invocations.
diff --git a/src/buildstream/_element.pyx b/src/buildstream/_element.pyx
index 388f295..cfa85de 100644
--- a/src/buildstream/_element.pyx
+++ b/src/buildstream/_element.pyx
@@ -14,7 +14,9 @@
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 #
-from .node cimport MappingNode, ScalarNode, SequenceNode
+from .node cimport MappingNode, Node, ScalarNode, SequenceNode
+from .sandbox._config import SandboxConfig
+from ._platform import Platform
 from ._variables cimport Variables
 
 
@@ -37,3 +39,47 @@
         element_public['split-rules'] = {}
 
     return element_public
+
+
+# Sandbox-specific configuration data, to be passed to the sandbox's constructor.
+#
+def extract_sandbox_config(object context, object project, object meta, MappingNode defaults):
+    cdef MappingNode sandbox_config
+
+    if meta.is_junction:
+        sandbox_config = Node.from_dict(
+            Node,
+            {
+                'build-uid': 0,
+                'build-gid': 0
+            },
+        )
+    else:
+        sandbox_config = (<MappingNode> project._sandbox).clone()
+
+    # The default config is already composited with the project overrides
+    cdef MappingNode sandbox_defaults = defaults.get_mapping('sandbox', default={})
+    sandbox_defaults = sandbox_defaults.clone()
+
+    sandbox_defaults._composite(sandbox_config)
+    (<MappingNode> meta.sandbox)._composite(sandbox_config)
+    sandbox_config._assert_fully_composited()
+
+    # Sandbox config, unlike others, has fixed members so we should validate them
+    sandbox_config.validate_keys(['build-uid', 'build-gid', 'build-os', 'build-arch'])
+
+    cdef str build_arch = sandbox_config.get_str('build-arch', default=None)
+    if build_arch:
+        build_arch = Platform.canonicalize_arch(build_arch)
+    else:
+        build_arch = context.platform.get_host_arch()
+
+    build_os = sandbox_config.get_str('build-arch', default=None)
+    if not build_os:
+        build_os = context.platform.get_host_os()
+
+    return SandboxConfig(
+        sandbox_config.get_int('build-uid'),
+        sandbox_config.get_int('build-gid'),
+        sandbox_config.get_str('build-os', default=build_os),
+        build_arch)
diff --git a/src/buildstream/element.py b/src/buildstream/element.py
index e22495f..edc2c14 100644
--- a/src/buildstream/element.py
+++ b/src/buildstream/element.py
@@ -98,11 +98,9 @@
 from . import _element  # type: any
 from . import _signals
 from . import _site
-from ._platform import Platform
 from .node import Node, _sentinel as _node_sentinel
 from .plugin import Plugin
 from .sandbox import SandboxFlags, SandboxCommandError
-from .sandbox._config import SandboxConfig
 from .sandbox._sandboxremote import SandboxRemote
 from .types import Consistency, CoreWarnings, Scope, _CacheBuildTrees, _KeyStrength
 from ._artifact import Artifact
@@ -334,7 +332,7 @@
             self.__remote_execution_specs = project.remote_execution_specs
 
         # Extract Sandbox config
-        self.__sandbox_config = self.__extract_sandbox_config(context, project, meta)
+        self.__sandbox_config = _element.extract_sandbox_config(context, project, meta, self.__defaults)
 
         self.__sandbox_config_supported = True
         if not self.__use_remote_execution():
@@ -2781,46 +2779,6 @@
 
         return config
 
-    # Sandbox-specific configuration data, to be passed to the sandbox's constructor.
-    #
-    @classmethod
-    def __extract_sandbox_config(cls, context, project, meta):
-        if meta.is_junction:
-            sandbox_config = Node.from_dict({
-                'build-uid': 0,
-                'build-gid': 0
-            })
-        else:
-            sandbox_config = project._sandbox.clone()
-
-        # Get the platform to ask for host architecture
-        platform = context.platform
-        host_arch = platform.get_host_arch()
-        host_os = platform.get_host_os()
-
-        # The default config is already composited with the project overrides
-        sandbox_defaults = cls.__defaults.get_mapping('sandbox', default={})
-        sandbox_defaults = sandbox_defaults.clone()
-
-        sandbox_defaults._composite(sandbox_config)
-        meta.sandbox._composite(sandbox_config)
-        sandbox_config._assert_fully_composited()
-
-        # Sandbox config, unlike others, has fixed members so we should validate them
-        sandbox_config.validate_keys(['build-uid', 'build-gid', 'build-os', 'build-arch'])
-
-        build_arch = sandbox_config.get_str('build-arch', default=None)
-        if build_arch:
-            build_arch = Platform.canonicalize_arch(build_arch)
-        else:
-            build_arch = host_arch
-
-        return SandboxConfig(
-            sandbox_config.get_int('build-uid'),
-            sandbox_config.get_int('build-gid'),
-            sandbox_config.get_str('build-os', default=host_os),
-            build_arch)
-
     # This makes a special exception for the split rules, which
     # elements may extend but whos defaults are defined in the project.
     #