Depuis la création du langage de programmation Python, sa philosophie fondamentale est de maximiser la lisibilité et la simplicité du code. La recherche de lisibilité et de simplicité de Python est presque folle. Un fait peut le confirmer : tant que vous entrez la commande "importer ceci" dans le répertoire racine du système Python et que vous appuyez sur la touche Entrée, un petit poème anglais sera immédiatement imprimé, ce qui signifie en gros en chinois :
« Beau vaut mieux que laid, explicite vaut mieux qu'implicite.
Simple vaut mieux que compliqué, complexe vaut mieux que compliqué,
plat vaut mieux qu'imbriqué, clairsemé vaut mieux que dense..."
Simple mieux que. compliqué, la lisibilité est importante. Il ne fait aucun doute que Python réussit effectivement très bien à atteindre ces objectifs : c’est de loin le langage le plus convivial à apprendre, et un programme Python ordinaire est généralement 5 à 10 fois plus court que le code C++ équivalent. Malheureusement, il y a un hic : la simplicité de Python se fait au détriment des performances. En fait, les programmes Python sont 10 à 100 fois plus lents que leurs homologues C++ ! Par conséquent, il semble qu’il existe un compromis permanent entre vitesse et simplicité, et il est impossible pour un langage de programmation d’avoir les deux.
Mais ne vous inquiétez pas, tout espoir n’est pas perdu.
Taichi offre le meilleur des deux mondes
Dans cet article, nous montrerons un exemple de programme écrit en utilisant Taichi. Ce programme utilise le GPU pour effectuer une simulation physique en temps réel d'un morceau de tissu tombant sur une sphère, tout en restituant simultanément les résultats.
Écrire un simulateur physique GPU en temps réel n'est jamais facile, mais le code source Taichi qui implémente cette routine est extrêmement simple. Le reste de cet article vous guidera tout au long de la mise en œuvre afin que vous puissiez avoir une idée de ce que propose Taichi, ainsi que de sa puissance et de sa convivialité.
Avant de commencer, autant deviner de combien de lignes de code se compose ce programme. Bien entendu, vous trouverez la réponse à la fin de l’article.
Aperçu de l'algorithme
Le mouvement de ce système de ressorts de masse est affecté par 4 facteurs :
gravitéLe programme commence
Pour utiliser Taichi dans un programme Python, vous devez d'abord importer Taichi en utilisant l'alias ti :
import taichi as ti
Si votre machine dispose d'un GPU Nvidia prenant en charge CUDA, les performances du programme Taichi seront maximisées. Si tel est le cas, ajoutez la ligne de code suivante après l'instruction d'importation ci-dessus :
ti.init(arch=ti.cuda)
Si vous n'avez pas de GPU CUDA, Taichi peut toujours être utilisé avec le vôtre via d'autres API graphiques telles que ti.metal, ti. Interaction GPU vulkan et ti.opengl. Cependant, la prise en charge de Taichi pour ces API n'est pas aussi complète que celle pour CUDA. Donc pour l'instant, nous utilisons le CPU comme backend de calcul :
ti.init(arch=ti.cpu)
Ne vous inquiétez pas, Taichi fonctionnera rapidement même s'il ne fonctionne qu'avec le CPU. Après avoir initialisé Taichi, nous pouvons commencer à déclarer la structure de données utilisée pour décrire le tissu du ressort de masse. Pour ce faire, nous ajoutons les lignes de code suivantes :
N = 128 x = ti.Vector.field(3, float, (N, N)) v = ti.Vector.field(3, float, (N, N))
Ces trois lignes déclarent x et v comme des tableaux bidimensionnels de taille N×N, où chaque élément du tableau est un vecteur tridimensionnel de nombres à virgule flottante. En Taichi, les tableaux sont appelés « champs » et les deux champs enregistrent respectivement la position et la vitesse de la masse ponctuelle. Notez que si vous initialisez Taichi pour qu'il s'exécute sur un GPU CUDA, ces champs/tableaux seront automatiquement stockés dans la mémoire du GPU. En plus du tissu, nous devons également définir la balle au milieu :
ball_radius = 0.2 ball_center = ti.Vector.field(3, float, (1,))
Ici, le centre de la balle est un champ 1D de taille 1, et son unique composant est un vecteur flottant 3D. Après avoir déclaré les champs obligatoires, initialisons ces champs avec les données correspondantes à t=0. Nous voulons nous assurer que, pour toute paire de points adjacents sur la même ligne ou colonne, la distance entre eux est égale à cell_size=1.0/N. Ceci est réalisé avec la routine d'initialisation suivante :
def init_scene(): for i, j in ti.ndrange(N, N): x[i, j] = ti.Vector([i * cell_size, j * cell_size / ti.sqrt(2), (N - j) * cell_size / ti.sqrt(2)]) ball_center[0] = ti.Vector([0.5, -0.5, 0.0])
Ici, vous n'avez pas à vous soucier de la signification de chaque valeur x[i,j] - elle est simplement choisie pour que le tissu tombe dans un coin de 45 degrés, reportez-vous à l'image ci-dessous.
在每个时间步中,我们的程序都会模拟影响布料运动的4个因素:重力、弹簧内力、阻尼和与红球的碰撞。其中,重力是最容易处理的。
下面是实现这一点的代码:
@ti.kernel def step(): for i in ti.grouped(v): v[i].y -= gravity * dt
这里有两点需要注意。首先,语句for i in ti.grouped(x)意味着将循环迭代x的所有元素,而不管x中有多少维度。其次,也是最重要的是:注解@ti.kernel意味着Taichi将自动并行运行函数中的任何顶级for循环。在本例中,Taichi将并行更新v中每个N*N向量的y分量。
接下来,我们来处理弦线的内力计算问题。首先,请注意前面图形中的每个质点最多连接到八个邻接质点。这些连接在我们的程序中表示如下:
links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1] links = [ti.Vector(v) for v in links]
从物理角度来看,系统中的每个弹簧s都用固定长度l(s,0)初始化。在任何时间t,如果s的当前长度l(s,t)超过l(s,0),则弹簧将在其端点上施加力,将它们拉在一起。相反,如果l(s,t)小于l(s,0),则弹簧会将端点彼此推开。这些力的大小始终与l(s,0)-l(s,0)的绝对值成正比。此交互由以下代码段捕获:
for i in ti.grouped(x): force = ti.Vector([0.0,0.0,0.0]) for d in ti.static(links): j = min(max(i + d, 0), [N-1,N-1]) relative_pos = x[j] - x[i] current_length = relative_pos.norm() original_length = cell_size * float(i-j).norm() if original_length != 0: force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length v[i] +=force * dt
请注意,这个for循环仍应作为substep函数中的顶级for循环,该函数用@ti.kernel注解。这样可以确保并行计算施加到每个质点的弹簧力。stiffness在此是一个常数,用于控制弹簧长度变化的程度。在上述程序中,我们使用stiffness =1600指定它的值。在现实世界中,当弹簧振动时,弹簧中储存的能量会消散到周围环境中,其振动最终停止。为了捕捉这种效应,在每个时间步,我们稍微降低每个点的速度大小:
for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt)
在此,damping取固定值2。
我们还需要处理布料和红球之间的碰撞。要做到这一点,我们只需将质点与球接触时的速度降低到0。这样可以确保布料“挂”在球上,而不是穿透球或向下滑动:
if (x[i]-ball_center[0]).norm() <= ball_radius: v[i] = ti.Vector([0.0, 0.0, 0.0])
最后,我们用每个质点的速度更新其自身的位置:
x[i] += dt * v[i]
这就是我们对一块质量弹簧布料进行并行模拟所需的全部代码。
我们将使用Taichi内置的基于GPU的GUI系统(昵称是“GGUI”)渲染布料。GGUI使用Vulkan图形API进行渲染,因此请确保您的计算机上安装了Vulkan(https://docs.taichi.graphics/lang/articles/misc/ggui)。GGUI支持渲染两种类型的3D对象:三角形网格和粒子。在我们的示例中,将把布料渲染为三角形网格,把红色球渲染为单个粒子。
GGUI表示一个三角形网格,包含两个Taichi场:一个顶点(vertices)场和一个索引(indices)场。顶点场是一个一维场,其中每个元素提取是一个表示顶点位置的三维向量,可能由多个三角形共享。在我们的应用程序中,每个点质量都是一个三角形顶点,因此我们可以简单地将数据从x复制到vertices:
vertices = ti.Vector.field(3, float, N * N) @ti.kernel def set_vertices(): for i, j in ti.ndrange(N, N): vertices[i * N + j] = x[i, j]
请注意,每一帧都需要调用set_vertices,因为顶点位置不断被模拟更新。
我们的布料是用一个质点的N×N网格表示,也可以被看作一个由(N-1)×(N-1)小正方形组成的网格。每个正方形都将渲染为两个三角形。因此,总共有(N-1)×(N-1)×2个三角形。每个三角形将在顶点场中表示为3个整数,该场记录顶点场中三角形顶点的索引。以下代码片段捕获了这一结构:
num_triangles = (N - 1) * (N - 1) * 2 indices = ti.field(int, num_triangles * 3) @ti.kernel def set_indices(): for i, j in ti.ndrange(N, N): if i < N - 1 and j < N - 1: square_id = (i * (N - 1)) + j #正方形的第一个小三角形 indices[square_id * 6 + 0] = i * N + j indices[square_id * 6 + 1] = (i + 1) * N + j indices[square_id * 6 + 2] = i * N + (j + 1) #正方形的第二个小三角形 indices[square_id * 6 + 3] = (i + 1) * N + j + 1 indices[square_id * 6 + 4] = i * N + (j + 1) indices[square_id * 6 + 5] = (i + 1) * N + j
请注意,与函数set_vertices不同,函数set_indices只需要调用一次。这是因为三角形顶点的索引实际上并没有改变——只是位置在改变。
为了将红球渲染为粒子,我们实际上不需要准备任何数据,我们之前定义的ball_center和ball_radius变量就是GGUI所需要的全部内容。
至此,我们已经介绍完本文示例程序的所有核心函数!下面代码展示了我们如何调用这些函数:
init() set_indices() window = ti.ui.Window("Cloth", (800, 800), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() camera = ti.ui.make_camera() while window.running: for i in range(30): step() set_vertices() camera.position(0.5, -0.5, 2) camera.lookat(0.5, -0.5, 0) scene.set_camera(camera) scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) canvas.scene(scene) window.show()
需要注意的一个小细节是,我们将在主程序循环中的每一帧调用函数step()30次,而不是调用一次。这样做的目的就是让动画不会运行得太慢。把上述所有代码放在一起,整个程序应该是这样的:
import taichi as ti ti.init(arch=ti.cuda) # 另一种可选择方案: ti.init(arch=ti.cpu) N = 128 cell_size = 1.0 / N gravity = 0.5 stiffness = 1600 damping = 2 dt = 5e-4 ball_radius = 0.2 ball_center = ti.Vector.field(3, float, (1,)) x = ti.Vector.field(3, float, (N, N)) v = ti.Vector.field(3, float, (N, N)) num_triangles = (N - 1) * (N - 1) * 2 indices = ti.field(int, num_triangles * 3) vertices = ti.Vector.field(3, float, N * N) def init_scene(): for i, j in ti.ndrange(N, N): x[i, j] = ti.Vector([i * cell_size , j * cell_size / ti.sqrt(2), (N - j) * cell_size / ti.sqrt(2)]) ball_center[0] = ti.Vector([0.5, -0.5, -0.0]) @ti.kernel def set_indices(): for i, j in ti.ndrange(N, N): if i < N - 1 and j < N - 1: square_id = (i * (N - 1)) + j # 1st triangle of the square indices[square_id * 6 + 0] = i * N + j indices[square_id * 6 + 1] = (i + 1) * N + j indices[square_id * 6 + 2] = i * N + (j + 1) # 2nd triangle of the square indices[square_id * 6 + 3] = (i + 1) * N + j + 1 indices[square_id * 6 + 4] = i * N + (j + 1) indices[square_id * 6 + 5] = (i + 1) * N + j links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]] links = [ti.Vector(v) for v in links] @ti.kernel def step(): for i in ti.grouped(x): v[i].y -= gravity * dt for i in ti.grouped(x): force = ti.Vector([0.0,0.0,0.0]) for d in ti.static(links): j = min(max(i + d, 0), [N-1,N-1]) relative_pos = x[j] - x[i] current_length = relative_pos.norm() original_length = cell_size * float(i-j).norm() if original_length != 0: force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length v[i] +=force * dt for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt) if (x[i]-ball_center[0]).norm() <= ball_radius: v[i] = ti.Vector([0.0, 0.0, 0.0]) x[i] += dt * v[i] @ti.kernel def set_vertices(): for i, j in ti.ndrange(N, N): vertices[i * N + j] = x[i, j] init_scene() set_indices() window = ti.ui.Window("Cloth", (800, 800), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() camera = ti.ui.make_camera() while window.running: for i in range(30): step() set_vertices() camera.position(0.5, -0.5, 2) camera.lookat(0.5, -0.5, 0) scene.set_camera(camera) scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) canvas.scene(scene) window.show()
注意到,上述代码总行数仅有91行!
我希望你喜欢本文中提供的上述示例程序!如果的确如此,下面几个不同挑战等级的任务留给你:
Enfin, passons en revue ce que Taichi nous a permis d'implémenter dans les 91 lignes de code Python ci-dessus :
Taichi nous permet non seulement d'implémenter toutes ces fonctionnalités complexes avec une petite quantité de code, mais cela nous évite d'avoir à apprendre CUDA, la programmation multi-thread ou le rendu GPU. Avec Taichi, n’importe qui peut écrire des programmes performants. Ils peuvent se concentrer sur les aspects algorithmiques du code et laisser les aspects performances au langage de programmation lui-même. Cela nous amène à la devise de Taichi : Programmation parallèle pour tous ! de nombreux exemples de projets Taichi, tous très intéressants. Enfin, si vous croyez également en la mission de développer un langage convivial et puissant pour le calcul parallèle, vous êtes plus que bienvenu pour rejoindre Taichi en tant que contributeur open source.
Dans mon prochain article, je discuterai du fonctionnement interne de Taichi et de la manière dont il interagit avec le GPU sur différentes plates-formes de calcul et de rendu. D'ici là, vous commencerez une joyeuse programmation Taichi ! Introduction au traducteur
Zhu Xianzhong, rédacteur de la communauté 51CTO, blogueur expert 51CTO, conférencier, professeur d'informatique dans une université de Weifang et vétéran de l'industrie de la programmation indépendante. Au début, il s'est concentré sur diverses technologies Microsoft (compilé trois livres techniques liés à ASP.NET AJX et Cocos 2d-X. Au cours des dix dernières années, il s'est consacré au monde open source (familier avec les logiciels complets populaires). technologie de développement Web de pile) et découvert OneNet/AliOS+Arduino/ESP32/Raspberry Pi et d'autres technologies de développement IoT et Scala+Hadoop+Spark+Flink et d'autres technologies de développement Big Data.
Titre original : Guide du débutant sur le calcul haute performance en Python, auteur : Dunfan Lu
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!