Skip to content

Commit 5bd8da5

Browse files
committed
True thread parallelism using the new emNewInterpreterOwnGIL thread execution mode in Python 12
1 parent ac1ba07 commit 5bd8da5

File tree

6 files changed

+219
-32
lines changed

6 files changed

+219
-32
lines changed

Demos/Demo33/SortThds.pas

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
unit SortThds;
22

33

4-
54
interface
65

76
uses
8-
Classes,
7+
Types, Classes,
98
Graphics, ExtCtrls, Forms,
109
PythonEngine;
1110

@@ -125,11 +124,11 @@ procedure TSortThread.ExecuteWithPython;
125124
try
126125
with GetPythonEngine do
127126
begin
128-
if Assigned(FModule) and (ThreadExecMode = emNewInterpreter) then
127+
if Assigned(FModule) and (ThreadExecMode <> emNewState) then
129128
FModule.InitializeForNewInterpreter;
130129
if Assigned(fScript) then
131130
ExecStrings(fScript);
132-
pyfunc := FindFunction( ExecModule, fpyfuncname);
131+
pyfunc := FindFunction(ExecModule, utf8encode(fpyfuncname));
133132
if Assigned(pyfunc) then
134133
try
135134
EvalFunction(pyfunc,[NativeInt(self),0,FSize]);
@@ -159,7 +158,7 @@ procedure TSortThread.Stop;
159158
function TSortThread.getvalue(i: integer): integer;
160159
begin
161160
if Terminated then
162-
raise EPythonError.Create( 'Pythonthread terminated');
161+
raise EPythonError.Create('Pythonthread terminated');
163162
Result := FSortArray^[i];
164163
end;
165164

Demos/Demo33/ThSort.pas

+4-1
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,17 @@ procedure TThreadSortForm.InitThreads(ThreadExecMode: TThreadExecMode; script: T
117117

118118
Thread1 := TSortThread.Create( ThreadExecMode, script, SortModule, 'SortFunc1',
119119
BubbleSortBox, BubbleSortArray);
120+
Thread1.InterpreterConfig := _PyInterpreterConfig_INIT;
120121
Thread1.OnTerminate := ThreadDone;
121122

122123
Thread2 := TSortThread.Create( ThreadExecMode, script, SortModule, 'SortFunc2',
123124
SelectionSortBox, SelectionSortArray);
125+
Thread2.InterpreterConfig := _PyInterpreterConfig_INIT;
124126
Thread2.OnTerminate := ThreadDone;
125127

126128
Thread3 := TSortThread.Create( ThreadExecMode, script, SortModule, 'SortFunc3',
127129
QuickSortBox, QuickSortArray);
130+
Thread3.InterpreterConfig := _PyInterpreterConfig_INIT;
128131
Thread3.OnTerminate := ThreadDone;
129132

130133
end;
@@ -144,7 +147,7 @@ procedure TThreadSortForm.Start1BtnClick(Sender: TObject);
144147

145148
procedure TThreadSortForm.Start3BtnClick(Sender: TObject);
146149
begin
147-
InitThreads(emNewInterpreter, PythonMemo.Lines);
150+
InitThreads(emNewInterpreterOwnGIL, PythonMemo.Lines);
148151
//PythonEngine1.ExecStrings(PythonMemo.Lines);
149152
end;
150153

Demos/Demo36/ParallelPython.dpr

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
program ParallelPython;
2+
3+
{$APPTYPE CONSOLE}
4+
5+
{$R *.res}
6+
7+
uses
8+
System.SysUtils,
9+
System.Diagnostics,
10+
System.Variants,
11+
System.SyncObjs,
12+
PythonEngine,
13+
VarPyth;
14+
15+
var
16+
PythonEngine: TPythonEngine;
17+
18+
procedure CreatePyEngine;
19+
begin
20+
PythonEngine := TPythonEngine.Create(nil);
21+
PythonEngine.Name := 'PythonEngine';
22+
PythonEngine.LoadDll;
23+
TPythonThread.Py_Begin_Allow_Threads;
24+
end;
25+
26+
procedure DestroyEngine;
27+
begin
28+
TPythonThread.Py_End_Allow_Threads;
29+
PythonEngine.Free;
30+
end;
31+
32+
const
33+
N = 5;
34+
Script =
35+
'import math'#10 +
36+
''#10 +
37+
'def is_prime(n):'#10 +
38+
' """ totally naive implementation """'#10 +
39+
' if n <= 1:'#10 +
40+
' return False'#10 +
41+
''#10 +
42+
' q = math.floor(math.sqrt(n))'#10 +
43+
' for i in range(2, q + 1):'#10 +
44+
' if (n % i == 0):'#10 +
45+
' return False'#10 +
46+
' return True'#10 +
47+
''#10 +
48+
''#10 +
49+
'def count_primes(max_n):'#10 +
50+
' res = 0'#10 +
51+
' for i in range(2, max_n + 1):'#10 +
52+
' if is_prime(i):'#10 +
53+
' res += 1'#10 +
54+
' return res'#10 +
55+
''#10 +
56+
'print("prime count", count_primes(1000000))'#10 +
57+
''#10;
58+
59+
var
60+
Event: TCountdownEvent;
61+
62+
type
63+
TPyThread = class(TPythonThread)
64+
protected
65+
procedure ExecuteWithPython; override;
66+
public
67+
constructor Create(ThreadMode: TThreadExecMode);
68+
end;
69+
70+
procedure TPyThread.ExecuteWithPython;
71+
begin
72+
GetPythonEngine.ExecString(Script);
73+
Event.Signal;
74+
end;
75+
76+
constructor TPyThread.Create(ThreadMode: TThreadExecMode);
77+
begin
78+
inherited Create;
79+
ThreadExecMode := ThreadMode;
80+
FreeOnTerminate := True;
81+
InterpreterConfig := _PyInterpreterConfig_INIT;
82+
end;
83+
84+
var
85+
SW: TStopwatch;
86+
I: Integer;
87+
begin
88+
try
89+
CreatePyEngine;
90+
try
91+
Event := TCountdownEvent.Create(N);
92+
WriteLn('Classic Subinterpreter:');
93+
SW := TStopwatch.StartNew;
94+
for I := 1 to N do
95+
TPyThread.Create(emNewState);
96+
Event.WaitFor;
97+
Event.Free;
98+
SW.Stop;
99+
WriteLn('Elapsed ms: ' + SW.ElapsedMilliseconds.ToString);
100+
WriteLn;
101+
102+
Event := TCountdownEvent.Create(N);
103+
WriteLn('Subinterpreter with own GIL:');
104+
SW := TStopwatch.StartNew;
105+
for I := 1 to N do
106+
TPyThread.Create(emNewInterpreterOwnGIL);
107+
Event.WaitFor;
108+
Event.Free;
109+
SW.Stop;
110+
WriteLn('Elapsed ms: ' + SW.ElapsedMilliseconds.ToString);
111+
WriteLn;
112+
finally
113+
DestroyEngine;
114+
end;
115+
except
116+
on E: Exception do
117+
Writeln(E.ClassName, ': ', E.Message);
118+
end;
119+
ReadLn;
120+
end.
121+

Demos/readme.txt

+1
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ Demo32 Demo08 revisited using WrapDelphi
3232
Demo33 Using Threads inside Python
3333
Demo34 Dynamically creating, destroying and recreating PythonEngine. Uses PythonVersions
3434
Demo35 Fast access to numpy arrays using the buffer protocol
35+
Demo36 True thread parallelism using the new emNewInterpreterOwnGIL thread execution mode in Python 12

Source/PythonEngine.pas

+70-19
Original file line numberDiff line numberDiff line change
@@ -956,28 +956,35 @@ PyBufferProcs = record
956956

957957
//bytearrayobject.h
958958

959-
//typedef struct {
960-
// PyObject_VAR_HEAD
961-
// Py_ssize_t ob_alloc; /* How many bytes allocated in ob_bytes */
962-
// char *ob_bytes; /* Physical backing buffer */
963-
// char *ob_start; /* Logical start inside ob_bytes */
964-
// Py_ssize_t ob_exports; /* How many buffer exports */
965-
//} PyByteArrayObject;
966-
967959
PyByteArrayObject = {$IFDEF CPUX86}packed{$ENDIF} record
968-
// Start of PyObject_VAR_HEAD
969-
// Start of the Head of an object
970-
ob_base: PyObject;
971-
ob_size: Py_ssize_t;
972-
// End of the Head of an object
960+
ob_refcnt: NativeInt;
961+
ob_type: PPyTypeObject;
962+
ob_alloc: Py_ssize_t;
973963
ob_bytes: PAnsiChar;
974964
ob_start: PAnsiChar;
975965
ob_exports: Py_ssize_t;
976966
end;
977967

968+
//initconfig.h
969+
970+
const
971+
_PyStatus_TYPE_OK = 0;
972+
_PyStatus_TYPE_ERROR = 1;
973+
_PyStatus_TYPE_EXIT = 2;
974+
975+
type
976+
TPyStatus_Type = Integer;
977+
978+
PyStatus = {$IFDEF CPUX86}packed{$ENDIF} record
979+
_type: TPyStatus_Type;
980+
func: PAnsiChar;
981+
err_msg: PAnsiChar;
982+
exitcode: Integer;
983+
end;
984+
978985
//#######################################################
979986
//## ##
980-
//## GIL state ##
987+
//## GIL related ##
981988
//## ##
982989
//#######################################################
983990
const
@@ -986,12 +993,40 @@ PyBufferProcs = record
986993
type
987994
PyGILState_STATE = type Integer; // (PyGILState_LOCKED, PyGILState_UNLOCKED);
988995

996+
// Introduced in Python 12
997+
const
998+
PyInterpreterConfig_DEFAULT_GIL = 0;
999+
PyInterpreterConfig_SHARED_GIL = 1;
1000+
PyInterpreterConfig_OWN_GIL = 2;
1001+
1002+
type
1003+
PPyInterpreterConfig = ^PyInterpreterConfig;
1004+
PyInterpreterConfig = {$IFDEF CPUX86}packed{$ENDIF} record
1005+
use_main_obmalloc: Integer;
1006+
allow_fork: Integer;
1007+
allow_exec: Integer;
1008+
allow_threads: Integer;
1009+
allow_daemon_threads: Integer;
1010+
check_multi_interp_extensions: Integer;
1011+
gil: Integer;
1012+
end;
1013+
1014+
const
1015+
_PyInterpreterConfig_INIT: PyInterpreterConfig =
1016+
( use_main_obmalloc: 0;
1017+
allow_fork: 0;
1018+
allow_exec: 0;
1019+
allow_threads: 1;
1020+
allow_daemon_threads: 0;
1021+
check_multi_interp_extensions: 1;
1022+
gil: PyInterpreterConfig_OWN_GIL);
1023+
9891024
//#######################################################
9901025
//## ##
9911026
//## New exception classes ##
9921027
//## ##
9931028
//#######################################################
994-
1029+
type
9951030
// Components' exceptions
9961031
EDLLLoadError = class(Exception);
9971032
EDLLImportError = class(Exception)
@@ -1709,6 +1744,7 @@ TPythonInterface=class(TDynamicDll)
17091744
Py_IsInitialized : function : integer; cdecl;
17101745
Py_GetProgramFullPath : function : PAnsiChar; cdecl;
17111746
Py_NewInterpreter : function : PPyThreadState; cdecl;
1747+
Py_NewInterpreterFromConfig : function( tstate: PPyThreadState; config: PPyInterpreterConfig): PyStatus; cdecl;
17121748
Py_EndInterpreter : procedure( tstate: PPyThreadState); cdecl;
17131749
PyEval_AcquireLock : procedure; cdecl;
17141750
PyEval_ReleaseLock : procedure; cdecl;
@@ -2798,7 +2834,7 @@ TPyVar = class(TPyObject)
27982834
//## Thread Object with Python interpreter lock ##
27992835
//## ##
28002836
//#######################################################
2801-
TThreadExecMode = (emNewState, emNewInterpreter);
2837+
TThreadExecMode = (emNewState, emNewInterpreter, emNewInterpreterOwnGIL);
28022838

28032839
{$HINTS OFF}
28042840
TPythonThread = class(TThread)
@@ -2813,6 +2849,7 @@ TPythonThread = class(TThread)
28132849
protected
28142850
procedure ExecuteWithPython; virtual; abstract;
28152851
public
2852+
InterpreterConfig: PyInterpreterConfig;
28162853
class procedure Py_Begin_Allow_Threads;
28172854
class procedure Py_End_Allow_Threads;
28182855
// The following procedures are redundant and only for
@@ -2863,6 +2900,7 @@ function SysVersionFromDLLName(const DLLFileName : string): string;
28632900
procedure PythonVersionFromDLLName(LibName: string; out MajorVersion, MinorVersion: integer);
28642901
function PythonVersionFromRegVersion(const ARegVersion: string;
28652902
out AMajorVersion, AMinorVersion: integer): boolean;
2903+
function PyStatus_Exception(const APyStatus: PyStatus): Boolean;
28662904

28672905
{ Helper functions}
28682906
(*
@@ -3950,6 +3988,8 @@ procedure TPythonInterface.MapDll;
39503988
Py_GetProgramFullPath := Import('Py_GetProgramFullPath');
39513989
Py_GetBuildInfo := Import('Py_GetBuildInfo');
39523990
Py_NewInterpreter := Import('Py_NewInterpreter');
3991+
if (FMajorVersion > 3) or (FMinorVersion >= 12) then
3992+
Py_NewInterpreterFromConfig := Import('Py_NewInterpreterFromConfig');
39533993
Py_EndInterpreter := Import('Py_EndInterpreter');
39543994
PyEval_AcquireLock := Import('PyEval_AcquireLock');
39553995
PyEval_ReleaseLock := Import('PyEval_ReleaseLock');
@@ -9237,8 +9277,9 @@ procedure TPyVar.SetValueFromVariant( const value : Variant );
92379277

92389278
procedure TPythonThread.Execute;
92399279
var
9240-
global_state : PPyThreadState;
9241-
gilstate : PyGILState_STATE;
9280+
global_state: PPyThreadState;
9281+
gilstate: PyGILState_STATE;
9282+
Status: PyStatus;
92429283
begin
92439284
with GetPythonEngine do
92449285
begin
@@ -9256,7 +9297,12 @@ procedure TPythonThread.Execute;
92569297
gilstate := PyGILState_Ensure();
92579298
global_state := PyThreadState_Get;
92589299
PyThreadState_Swap(nil);
9259-
fThreadState := Py_NewInterpreter;
9300+
9301+
if (fThreadExecMode = emNewInterpreter) or
9302+
((FMajorVersion = 3) and (FMinorVersion < 12)) or
9303+
PyStatus_Exception(Py_NewInterpreterFromConfig(@fThreadState, @InterpreterConfig))
9304+
then
9305+
fThreadState := Py_NewInterpreter;
92609306

92619307
if Assigned( fThreadState) then
92629308
begin
@@ -9705,5 +9751,10 @@ function PythonVersionFromRegVersion(const ARegVersion: string;
97059751
Result := (AMajorVersion > 0) and (AMinorVersion > 0);
97069752
end;
97079753

9754+
function PyStatus_Exception(const APyStatus: PyStatus): Boolean;
9755+
begin
9756+
Result := APyStatus._type <> _PyStatus_TYPE_OK;
9757+
end;
9758+
97089759
end.
97099760

0 commit comments

Comments
 (0)