Bonsoir!

J'apprends Python en créant une petite appli qui explore l'ensemble de Mandelbrot. Le coeur de cette appli est la fonction qui dessine l'ensemble ou une partie de l'ensemble. Les calculs peuvent parfois être longs, et je désirais ajouter un bouton "Stop". C'était compliqué d'interrompre les boucles quand la fonction était exécutée dans la main loop, et beaucoup plus facile si la fonction s'exécute dans un thread secondaire.

J'ai donc placé l'appel de la fonction dans un thread et là, surprise, pour des valeurs très petites (1e-28), j'ai commencé a voir apparaître des distorsions dans l'image. Par contre, la même partie de l'ensemble calculée avec une valeur de 1e-27 ne pose pas de problème.

Pour résumer: la même fonction marche très bien quand appelée dans la main loop, et débloque quand appelée depuis un thread, avec évidemment les mêmes paramètres dans les deux cas.

Dans le code suivant, je force le calcul de seulement un pixel sur 4, sinon les temps de calcul sont vraiment longs, mais la distorsion de l'image est bien visible.
Si vous voulez calculer tous les pixels, il suffit de modifier à deux endroits la variable loop_step et de la mettre à 1.

J'ai évidemment simplifié le code au maximum pour ne garder que les lignes pertinentes à la reproduction du problème.

J'ai exploré les pistes auxquelles j'ai pensé (vérifier le garbage collection, le nombre de blocs mémoire alloués,...) mais je n'ai rien trouvé de probant.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
 
import time
import decimal
from tkinter import *
from decimal import getcontext
from PIL import Image, ImageDraw, ImageTk
import threading
from threading import Event
 
exit_event = Event()
 
getcontext().prec = 50
 
interface_frame_width = 200
root_horizontal_margin = 0
root_vertical_margin = 74
 
colors_blue = ("#000000", "#000102", "#000205", "#000409", "#000511",
               "#010615", "#010617", "#01081c", "#010a23", "#020c28",
               "#030e30", "#031037", "#04133f", "#051646", "#07194e",
               "#081d58", "#081d59", "#091e5b", "#0a1f5d", "#0b1f5f",
               "#0b1f5f", "#0c2061", "#0d2163", "#0f2267", "#102368",
               "#11246c", "#12256e", "#132670", "#142772", "#152774",
               "#162876", "#173078", "#172978", "#182979", "#192a7b",
               "#1a2b7d", "#1b2c7f", "#1c2d83", "#1d2e85", "#1e2f87",
               '#1f2f88', '#20308a', '#22318e', '#233290', '#243392',
               '#243494', '#243594', '#243895', '#243996', '#243c97',
               '#243d98', '#243e99', '#24419a', '#23429a', '#23459c',
               '#23479d', '#23499e', '#234a9e', '#234b9f', '#234da0',
               '#234ea0', '#234fa1', '#2251a1', '#2252a2', '#2253a3',
               '#2255a3', '#2256a4', '#2257a5', '#2259a5', '#225aa6',
               '#225ba6', '#225da7', '#215ea8', '#2160a8', '#2161a9',
               '#2163aa', '#2164ab', '#2166ab', '#2168ac', '#2069ad',
               '#206bae', '#206cae', '#2070b0', '#2071b1', '#1f73b1',
               '#1f74b2', '#1f76b3', '#1f78b4', '#1f79b4', '#1f7bb5',
               '#1e7cb6', '#1e7eb7', '#1e80b8', '#1e83b9', '#1e84ba',
               '#1e86bb', '#1d88bb', '#1d89bc', '#1d8bbd', '#1d8cbe',
               '#1d8ebe', '#1d90bf', '#1d91c0', '#1e92c0', '#1f93c0',
               '#2094c0', '#2196c0', '#2397c0', '#2498c0', '#2599c0',
               '#269ac1', '#279bc1', '#289dc1', '#299ec1', '#2a9fc1',
               '#2ca0c1', '#2da1c1', '#2ea2c1', '#2fa4c2', '#30a5c2',
               '#31a6c2', '#32a7c2', '#34a8c2', '#35a9c2', '#36aac2',
               '#37acc2', '#38adc3', '#39aec3', '#3aafc3', '#3bb0c3',
               '#3db1c3', '#3eb3c3', '#3fb4c3', '#40b5c3', '#41b6c3',
               '#43b7c3', '#45b7c3', '#47b8c3', '#49b9c2', '#4bb9c2',
               '#4dbac2', '#4fbbc1', '#51bcc1', '#53bcc1', '#55bdc1',
               '#57bec0', '#59bfc0', '#5bbfc0', '#5dc0bf', '#5fc1bf',
               '#61c1bf', '#63c2bf', '#64c3be', '#66c4be', '#68c4be',
               '#6ac5bd', '#6cc6bd', '#6ec6bd', '#70c7bd', '#72c8bc',
               '#74c9bc', '#76c9bc', '#78cabb', '#7acbbb', '#7cccbb',
               '#7eccbb', '#80cdba', '#82ceba', '#84cfba', '#87d0ba',
               '#89d1b9', '#8bd1b9', '#8dd2b9', '#90d3b9', '#92d4b9',
               '#94d5b8', '#97d6b8', '#99d7b8', '#9bd8b8', '#9dd8b8',
               '#a0d9b7', '#a2dab7', '#a4dbb7', '#a6dcb7', '#a9ddb6',
               '#abdeb6', '#addfb6', '#afdfb6', '#b2e0b6', '#b6e2b5',
               '#b7e2b5', '#b8e3b5', '#b9e3b5', '#bbe4b5', '#bde5b4',
               '#bfe6b4', '#c1e7b4', '#c4e7b4', '#c6e8b4', '#c7e9b3',
               '#c9e9b3', '#caeab3', '#cbeab3', '#ccebb3', '#cdebb3',
               '#cfecb3', '#d0ecb3', '#d1edb3', '#d2edb3', '#d3eeb2',
               '#d5eeb2', '#d6eeb3', '#d7efb4', '#d8f0b4', '#d9f0b7',
               '#daf0b6', '#daf0b2', '#dcf1b2', '#ddf1b2', '#def2b2',
               '#dff2b2', '#e0f3b1', '#e2f3b1', '#e3f4b1', '#e4f4b1',
               '#e5f5b1', '#e6f5b1', '#e8f6b1', '#e9f6b1', '#eaf7b1',
               '#ebf7b1', '#ecf7b1', '#edf8b2', '#eef8b3', '#eef8b4',
               '#eff8b5', '#eff9b7', '#f0f9b8', '#f0f9b9', '#f1f9ba',
               '#f2f9bc', '#f2fabd', '#f3fabe', '#f3fabf', '#f4fac1')
 
 
class DrawingProcess(threading.Thread):
 
    def __init__(self):
        threading.Thread.__init__(self)
        self.compteur = 1
        self.go = False
 
    def run(self):
 
        while not exit_event.is_set():
 
            if self.go:
 
                x_min = decimal.Decimal("-1.4492083625918471911137774497")
                y_min = decimal.Decimal("-0.0219455526775311555223130111")
                xy_step = decimal.Decimal(1E-28)
                color_offset = 300
                loop_step = 4
 
                myimage.draw_me(x_min, y_min, xy_step, color_offset, loop_step)
 
            # end if
 
        # end while
 
    # end function run
 
 
# end class
 
 
class Configuration:
 
    def __init__(self):
 
        self.rows_number = 0
        self.lines_number = 0
        self.interface_width = 0
 
    def set_dimensions(self, horizontal_margin, vertical_margin, interface):
 
        self.rows_number = root.winfo_screenwidth() - horizontal_margin - interface
        self.lines_number = root.winfo_screenheight() - vertical_margin
        self.interface_width = interface
 
 
# end classe Configuration
 
 
class Drawing:
 
    def __init__(self):
 
        self.x_min = 0
        self.y_min = 0
        self.xy_step = 0
        self.color_offset = 0
        self.loop_step = 0
        self.drawing_done = False
 
    def draw_me(self, x_min, y_min, xy_step, color_offset, loop_step):
 
        global image_tk
 
        the_start = time.perf_counter()
        self.drawing_done = False
 
        root.title("Calculs en cours...")
        root.update()
 
        nb_colors = len(colors_blue) - 1
        max_iterations = nb_colors + color_offset
 
        cnv.delete("all")
        draw.rectangle((0, 0, myconfig.rows_number, myconfig.lines_number), fill=(0, 0, 0))
 
        x_pixel = 1
        drawing_process.compteur = 1
 
        while drawing_process.compteur <= myconfig.rows_number:
 
            y_pixel = 1
 
            while y_pixel <= myconfig.lines_number:
 
                x_value = x_min + x_pixel * xy_step
                y_value = y_min + y_pixel * xy_step
                x = decimal.Decimal(0)
                y = decimal.Decimal(0)
 
                pixel_color = "white"
 
                x_carre = 0
                y_carre = 0
                iterations = 0
 
                while x_carre + y_carre <= 4 and iterations <= max_iterations:
 
                    x_temp = x_carre - y_carre + x_value
                    y = 2 * x * y + y_value
                    x = x_temp
 
                    iterations = iterations + 1
 
                    x_carre = x * x
                    y_carre = y * y
 
                # end while
 
                array_index = iterations - color_offset - 1
 
                if array_index < 1:
                    array_index = 1
 
                pixel_color = colors_blue[array_index]
                draw.rectangle([x_pixel, y_pixel, x_pixel + loop_step, y_pixel + loop_step], pixel_color)
 
                y_pixel = y_pixel + loop_step
 
            # end while y_pixel
 
            x_pixel = x_pixel + loop_step
            drawing_process.compteur = drawing_process.compteur + loop_step
 
        # end while x_pixel
 
        image_tk = ImageTk.PhotoImage(pil_image)
 
        cnv.create_image(0, 0, anchor=NW, image=image_tk)
        cnv.update()
 
        root.title("Calculs terminés en " + str(time.perf_counter() - the_start)[:4] + " secondes")
        root.update()
 
        self.drawing_done = True
        drawing_process.go = False
 
    # end function draw_me
 
# end class Drawing
 
 
def launch_in_main():
 
    x_min = decimal.Decimal("-1.4492083625918471911137774497")
    y_min = decimal.Decimal("-0.0219455526775311555223130111")
    xy_step = decimal.Decimal(1E-28)
    color_offset = 300
    loop_step = 4
 
    myimage.draw_me(x_min, y_min, xy_step, color_offset, loop_step)
 
# end function launch_drawing
 
 
def launch_in_thread():
 
    drawing_process.go = True
 
# end function launch_drawing
 
 
root = Tk()
root.resizable(False, False)
root.title("")
 
geometry_string = str(root.winfo_screenwidth() - root_horizontal_margin)
geometry_string = geometry_string + "x"
geometry_string = geometry_string + str(root.winfo_screenheight() - root_vertical_margin)
geometry_string = geometry_string + "+0+0"
 
root.geometry(geometry_string)
root.configure(background="#454545")
 
myconfig = Configuration()
myconfig.set_dimensions(root_horizontal_margin, root_vertical_margin, interface_frame_width)
 
left_frame = Frame(root)
left_frame.pack(side=LEFT)
 
right_frame = Frame(root, bg="#454545")
right_frame.pack(side=TOP, fill='both', expand=True, padx=10)
 
cnv = Canvas(left_frame, width=myconfig.rows_number, height=myconfig.lines_number, bg='black', highlightthickness=0)
cnv.pack(side=LEFT)
 
butn_in_main = Button(right_frame, text="Calculer dans Main", bg="#777777", command=launch_in_main)
butn_in_main.pack(side=TOP, fill=X, pady=2)
 
butn_in_thread = Button(right_frame, text="Calculer Dans Thread", bg="#777777", command=launch_in_thread)
butn_in_thread.pack(side=TOP, fill=X, pady=2)
 
myimage = Drawing()
myimage.drawing_done = True
 
pil_image = Image.new("RGB", (myconfig.rows_number, myconfig.lines_number))
draw = ImageDraw.Draw(pil_image)
 
drawing_process = DrawingProcess()
drawing_process.start()
 
root.mainloop()
Les problèmes de mémoire semblent être courants dans les applications qui jouent avec Mandelbrot, j'ai donc cherché des informations au sujet de la façon dont Python allouait la mémoire dans la main loop versus dans un thread, mais je n'ai rien trouvé...

Merci d'avance pour vos avis éclairés.