pycall 1.2.1 → 1.3.0.dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6430d6cc17470fa4644b81bbe57f8362ac4ea97bf21f1006f6b63a3fcb2e12fa
4
- data.tar.gz: 4251c7fd6fcd5e80b2ed99f45646189170a486ee76d7484a57d26dbcbaf4c71f
3
+ metadata.gz: 026f371fe891d8a8ef0fc80caefc2c22b1cefc22e937d3699d2aec0a42239d07
4
+ data.tar.gz: 51d1a6b56e187ceee4954eddf0d48e0c32d51d350e306ed05f6c7624bea914f4
5
5
  SHA512:
6
- metadata.gz: f3fa8077a0a4cc76a3f620cb5f937822238b2190240b38d93f5791c3d9fd410efba82b9964ef6defa3d9732fa10385e5e9db5c2e28ea9b969b1597d83b40e20c
7
- data.tar.gz: a36770a01ffe893a2fdc301658af30e683ac0a65e813134fe90a52ebcbf7baf8e9cf65e09332910a632108d35a10defc44db0faaf82bef5270c4bb427fa42f2c
6
+ metadata.gz: 4e7c254cbd02e82a401ab75be52c84738d075b6cbc82c31013513d383af529746afa74df272a84432962d1fe1f0d4eca79a8688d149790ab0ccecf1e7cff679e
7
+ data.tar.gz: a7f31a993b56cd8b15df2d5d5c2847f71382bef26e26f3723194e71addbf44d35b2939d641a906f8ea12de796befc8f2bf975670db5edf2dd01855c1cac08d2b
data/CHANGES.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # The change history of PyCall
2
2
 
3
+ ## master
4
+
5
+ * Add `PyCall.without_gvl` for explicitly releasing the RubyVM GVL
6
+
3
7
  ## 1.2.1
4
8
 
5
9
  * Prevent circular require in pycall/iruby.rb
data/README.md CHANGED
@@ -61,6 +61,45 @@ the `Math.sin` in Ruby:
61
61
  Type conversions from Ruby to Python are automatically performed for numeric,
62
62
  boolean, string, arrays, and hashes.
63
63
 
64
+ ### Releasing the RubyVM GVL during Python function calls
65
+
66
+ You may want to release the RubyVM GVL when you call a Python function that takes very long runtime.
67
+ PyCall provides `PyCall.without_gvl` method for such purpose. When PyCall performs python function call,
68
+ PyCall checks the current context, and then it releases the RubyVM GVL when the current context is in a `PyCall.without_gvl`'s block.
69
+
70
+ ```ruby
71
+ PyCall.without_gvl do
72
+ # In this block, all Python function calls are performed without
73
+ # the GVL acquisition.
74
+ pyobj.long_running_function()
75
+ end
76
+
77
+ # Outside of PyCall.without_gvl block,
78
+ # all Python function calls are performed with the GVL acquisition.
79
+ pyobj.long_running_function()
80
+ ```
81
+
82
+ ### Debugging python finder
83
+
84
+ When you encounter `PyCall::PythonNotFound` error, you can investigate PyCall's python finder by setting `PYCALL_DEBUG_FIND_LIBPYTHON` environment variable to `1`. You can see the log like below:
85
+
86
+ ```
87
+ $ PYCALL_DEBUG_FIND_LIBPYTHON=1 ruby -rpycall -ePyCall.builtins
88
+ DEBUG(find_libpython) find_libpython(nil)
89
+ DEBUG(find_libpython) investigate_python_config("python3")
90
+ DEBUG(find_libpython) libs: ["Python.framework/Versions/3.7/Python", "Python", "libpython3.7m", "libpython3.7", "libpython"]
91
+ DEBUG(find_libpython) libpaths: ["/opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib", "/opt/brew/opt/python/lib", "/opt/brew/opt/python/Frameworks", "/opt/brew/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7", "/opt/brew/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib"]
92
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/Python.framework/Versions/3.7/Python
93
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/Python.framework/Versions/3.7/Python.dylib
94
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/darwin/Python.framework/Versions/3.7/Python
95
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/darwin/Python.framework/Versions/3.7/Python.dylib
96
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/Python.framework/Versions/3.7/Python
97
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/Python.framework/Versions/3.7/Python.dylib
98
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/darwin/Python.framework/Versions/3.7/Python
99
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/darwin/Python.framework/Versions/3.7/Python.dylib
100
+ DEBUG(find_libpython) dlopen("/opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/Python") = #<Fiddle::Handle:0x00007fc012048650>
101
+ ```
102
+
64
103
  ## PyCall object system
65
104
 
66
105
  PyCall wraps pointers of Python objects in `PyCall::PyPtr` objects.
@@ -2,7 +2,7 @@ require 'pycall/import'
2
2
  include PyCall::Import
3
3
 
4
4
  pyimport 'numpy', as: :np
5
- pyfrom 'sklearn.cross_validation', import: :train_test_split
5
+ pyfrom 'sklearn.model_selection', import: :train_test_split
6
6
  pyfrom 'sklearn.preprocessing', import: :StandardScaler
7
7
  pyfrom 'sklearn.datasets', import: %i(make_moons make_circles make_classification)
8
8
  pyfrom 'sklearn.neighbors', import: :KNeighborsClassifier
@@ -20,7 +20,7 @@ num_bins = 50
20
20
 
21
21
  fig, ax = *plt.subplots
22
22
 
23
- n, bins, patches = *ax.hist(x, num_bins, normed: 1)
23
+ n, bins, patches = *ax.hist(x, num_bins, density: 1)
24
24
 
25
25
  y = mlab.normpdf(bins, mu, sigma)
26
26
  ax.plot(bins, y, '--')
@@ -38,7 +38,7 @@
38
38
  "include PyCall::Import\n",
39
39
  "\n",
40
40
  "pyimport 'numpy', as: :np\n",
41
- "pyfrom 'sklearn.cross_validation', import: :train_test_split\n",
41
+ "pyfrom 'sklearn.model_selection', import: :train_test_split\n",
42
42
  "pyfrom 'sklearn.preprocessing', import: :StandardScaler\n",
43
43
  "pyfrom 'sklearn.datasets', import: %i(make_moons make_circles make_classification)\n",
44
44
  "pyfrom 'sklearn.neighbors', import: :KNeighborsClassifier\n",
@@ -70,6 +70,52 @@ pycall_after_fork(VALUE mod)
70
70
  return Qnil;
71
71
  }
72
72
 
73
+ static volatile pycall_tls_key without_gvl_key;
74
+
75
+ int
76
+ pycall_without_gvl_p(void)
77
+ {
78
+ /*
79
+ * In pthread, the default value is NULL (== 0).
80
+ *
81
+ * In Win32 thread, the default value is 0 (initialized by TlsAlloc).
82
+ */
83
+ return (int)pycall_tls_get(without_gvl_key);
84
+ }
85
+
86
+ static inline int
87
+ pycall_set_without_gvl(void)
88
+ {
89
+ return pycall_tls_set(without_gvl_key, (void *)1);
90
+ }
91
+
92
+ static inline int
93
+ pycall_set_with_gvl(void)
94
+ {
95
+ return pycall_tls_set(without_gvl_key, (void *)0);
96
+ }
97
+
98
+ VALUE
99
+ pycall_without_gvl(VALUE (* func)(VALUE), VALUE arg)
100
+ {
101
+ int state;
102
+ VALUE result;
103
+
104
+ pycall_set_without_gvl();
105
+
106
+ result = rb_protect(func, arg, &state);
107
+
108
+ pycall_set_with_gvl();
109
+
110
+ return result;
111
+ }
112
+
113
+ static VALUE
114
+ pycall_m_without_gvl(VALUE mod)
115
+ {
116
+ return pycall_without_gvl(rb_yield, Qnil);
117
+ }
118
+
73
119
  /* ==== PyCall::PyPtr ==== */
74
120
 
75
121
  const rb_data_type_t pycall_pyptr_data_type = {
@@ -890,7 +936,7 @@ struct call_pyobject_call_params {
890
936
  PyObject *kwargs;
891
937
  };
892
938
 
893
- PyObject *
939
+ static inline PyObject *
894
940
  call_pyobject_call(struct call_pyobject_call_params *params)
895
941
  {
896
942
  PyObject *res;
@@ -899,7 +945,7 @@ call_pyobject_call(struct call_pyobject_call_params *params)
899
945
  }
900
946
 
901
947
  PyObject *
902
- pyobject_call_without_gvl(PyObject *pycallable, PyObject *args, PyObject *kwargs)
948
+ pyobject_call(PyObject *pycallable, PyObject *args, PyObject *kwargs)
903
949
  {
904
950
  PyObject *res;
905
951
  struct call_pyobject_call_params params;
@@ -907,9 +953,14 @@ pyobject_call_without_gvl(PyObject *pycallable, PyObject *args, PyObject *kwargs
907
953
  params.args = args;
908
954
  params.kwargs = kwargs;
909
955
 
910
- res = (PyObject *)rb_thread_call_without_gvl(
911
- (void * (*)(void *))call_pyobject_call, (void *)&params,
912
- (rb_unblock_function_t *)pycall_interrupt_python_thread, NULL);
956
+ if (pycall_without_gvl_p()) {
957
+ res = (PyObject *)rb_thread_call_without_gvl(
958
+ (void * (*)(void *))call_pyobject_call, (void *)&params,
959
+ (rb_unblock_function_t *)pycall_interrupt_python_thread, NULL);
960
+ }
961
+ else {
962
+ res = call_pyobject_call(&params);
963
+ }
913
964
 
914
965
  return res;
915
966
  }
@@ -961,7 +1012,7 @@ pycall_call_python_callable(PyObject *pycallable, int argc, VALUE *argv)
961
1012
  }
962
1013
  }
963
1014
 
964
- res = pyobject_call_without_gvl(pycallable, args, kwargs); /* New reference */
1015
+ res = pyobject_call(pycallable, args, kwargs); /* New reference */
965
1016
  if (!res) {
966
1017
  pycall_pyerror_fetch_and_raise("PyObject_Call in pycall_call_python_callable");
967
1018
  }
@@ -2185,6 +2236,9 @@ Init_pycall(void)
2185
2236
 
2186
2237
  rb_define_module_function(mPyCall, "after_fork", pycall_after_fork, 0);
2187
2238
 
2239
+ pycall_tls_create(&without_gvl_key);
2240
+ rb_define_module_function(mPyCall, "without_gvl", pycall_m_without_gvl, 0);
2241
+
2188
2242
  /* PyCall::PyPtr */
2189
2243
 
2190
2244
  cPyPtr = rb_define_class_under(mPyCall, "PyPtr", rb_cBasicObject);
@@ -11,10 +11,19 @@ extern "C" {
11
11
  #include <ruby.h>
12
12
  #include <ruby/encoding.h>
13
13
  #include <ruby/thread.h>
14
+
14
15
  #include <assert.h>
15
16
  #include <inttypes.h>
16
17
  #include <limits.h>
17
18
 
19
+ #if defined(_WIN32)
20
+ # define PYCALL_THREAD_WIN32
21
+ # include <ruby/win32.h>
22
+ #elif defined(HAVE_PTHREAD_H)
23
+ # define PYCALL_THREAD_PTHREAD
24
+ # include <pthread.h>
25
+ #endif
26
+
18
27
  #if SIZEOF_LONG == SIZEOF_VOIDP
19
28
  # define PTR2NUM(x) (LONG2NUM((long)(x)))
20
29
  # define NUM2PTR(x) ((void*)(NUM2ULONG(x)))
@@ -492,6 +501,23 @@ extern PyTypeObject PyRuby_Type;
492
501
 
493
502
  PyObject * PyRuby_New(VALUE ruby_object);
494
503
 
504
+ /* ==== thread support ==== */
505
+
506
+ #if defined(PYCALL_THREAD_WIN32)
507
+ typedef DWORD pycall_tls_key;
508
+ #elif defined(PYCALL_THREAD_PTHREAD)
509
+ typedef pthread_key_t pycall_tls_key;
510
+ #else
511
+ # error "unsupported thread type"
512
+ #endif
513
+
514
+ int pycall_tls_create(pycall_tls_key* tls_key);
515
+ void *pycall_tls_get(pycall_tls_key tls_key);
516
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr);
517
+
518
+ int pycall_without_gvl_p(void);
519
+ VALUE pycall_without_gvl(VALUE (* func)(VALUE), VALUE arg);
520
+
495
521
  /* ==== pycall ==== */
496
522
 
497
523
  typedef struct {
@@ -0,0 +1,36 @@
1
+ #include "pycall_internal.h"
2
+
3
+ #if defined(PYCALL_THREAD_WIN32)
4
+ int pycall_tls_create(pycall_tls_key *tls_key)
5
+ {
6
+ *tls_key = TlsAlloc();
7
+ return *tls_key == TLS_OUT_OF_INDEXES;
8
+ }
9
+
10
+ void *pycall_tls_get(pycall_tls_key tls_key)
11
+ {
12
+ return TlsGetValue(tls_key);
13
+ }
14
+
15
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr)
16
+ {
17
+ return 0 == TlsSetValue(tls_key, ptr);
18
+ }
19
+ #endif
20
+
21
+ #if defined(PYCALL_THREAD_PTHREAD)
22
+ int pycall_tls_create(pycall_tls_key *tls_key)
23
+ {
24
+ return pthread_key_create(tls_key, NULL);
25
+ }
26
+
27
+ void *pycall_tls_get(pycall_tls_key tls_key)
28
+ {
29
+ return pthread_getspecific(tls_key);
30
+ }
31
+
32
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr)
33
+ {
34
+ return pthread_setspecific(tls_key, ptr);
35
+ }
36
+ #endif
@@ -27,12 +27,12 @@ module PyCall
27
27
  def find_python_config(python = nil)
28
28
  python ||= DEFAULT_PYTHON
29
29
  Array(python).each do |python_cmd|
30
- python_config = investigate_python_config(python_cmd)
31
- return [python_cmd, python_config] unless python_config.empty?
30
+ begin
31
+ python_config = investigate_python_config(python_cmd)
32
+ return [python_cmd, python_config] unless python_config.empty?
33
+ rescue
34
+ end
32
35
  end
33
- rescue
34
- raise ::PyCall::PythonNotFound
35
- else
36
36
  raise ::PyCall::PythonNotFound
37
37
  end
38
38
 
@@ -1,3 +1,9 @@
1
1
  module PyCall
2
- VERSION = "1.2.1"
2
+ VERSION = "1.3.0-dev"
3
+
4
+ module Version
5
+ numbers, TAG = VERSION.split("-")
6
+ MAJOR, MINOR, MICRO = numbers.split(".").map(&:to_i)
7
+ STRING = VERSION
8
+ end
3
9
  end
@@ -5,7 +5,13 @@ require 'pycall/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "pycall"
8
- spec.version = PyCall::VERSION
8
+ version_components = [
9
+ PyCall::Version::MAJOR.to_s,
10
+ PyCall::Version::MINOR.to_s,
11
+ PyCall::Version::MICRO.to_s,
12
+ PyCall::Version::TAG,
13
+ ]
14
+ spec.version = version_components.compact.join(".")
9
15
  spec.authors = ["Kenta Murata"]
10
16
  spec.email = ["mrkn@mrkn.jp"]
11
17
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pycall
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0.dev
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenta Murata
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-19 00:00:00.000000000 Z
11
+ date: 2019-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -171,6 +171,7 @@ files:
171
171
  - ext/pycall/pycall_internal.h
172
172
  - ext/pycall/range.c
173
173
  - ext/pycall/ruby_wrapper.c
174
+ - ext/pycall/thread.c
174
175
  - images/pycallrb_logo.png
175
176
  - images/pycallrb_logo_200.png
176
177
  - lib/pycall.rb
@@ -215,11 +216,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
215
216
  version: '0'
216
217
  required_rubygems_version: !ruby/object:Gem::Requirement
217
218
  requirements:
218
- - - ">="
219
+ - - ">"
219
220
  - !ruby/object:Gem::Version
220
- version: '0'
221
+ version: 1.3.1
221
222
  requirements: []
222
- rubygems_version: 3.0.2
223
+ rubygems_version: 3.0.3
223
224
  signing_key:
224
225
  specification_version: 4
225
226
  summary: pycall