π
<-
Chat plein-écran
[^]

QCC 2020 épisode 4 : Python et tas (heap)

Online

QCC 2020 épisode 4 : Python et tas (heap)

Unread postby critor » 08 Aug 2020, 22:40

5409
Quelle Calculatrice programmable Choisir 2020


Episode 4 - Python et tas (heap)


Les interpréteurs
MicroPython
ou similaires qu'elles font tourner font appel à 3 types de mémoires avec les rôles suivants :
  • la mémoire de stockage qui accueille et conserve tes scripts
  • le
    stack
    (pile)
    qui, à l'exécution, accueille les références vers les objets créés
  • le
    heap
    (tas)
    qui, à l'exécution, accueille le contenu de ces objets
En gros le
stack
limite donc le nombre d'objets différents pouvant exister simultanément en mémoire, alors que le
heap
limite la taille globale occupée par le contenu de ces objets.

Dans l'épisode précédent nous avons comparé les tailles de
stack
offertes par les calculatrices programmables en langage
Python
.

Aujourd'hui nous allons nous intéresser au
heap
. Cet espace est extrêmement important et surtout sur les plateformes nomades, car contrairement à d'autres langages les objets
Python
les plus simples ont le défaut d'être assez gros. Ce sera le plus souvent le
heap
le facteur le plus limitant pour tes projets.



Le temps de construire ensemble notre protocole de tests, commençons par les
TI-83 Premium CE Edition Python
et
TI-84 Plus CE-T Edition Python
. Elles sont hautement intéressantes pour comprendre ce qui se passe, puisque disposant du module
Python
gc
. Le module
gc
nous offre en effet plusieurs fonctions bien utiles ici :
  • gc.collect() pour nettoyer le
    heap
    en supprimant les valeurs d'objets
    Python
    qui ne sont plus référencées
  • gc.mem_alloc() pour connaître la consommation du
    heap
    en octets
  • gc.mem_free() pour connaître l'espace
    heap
    disponible en octets

Les
TI-83 Premium CE Edition Python
et
TI-84 Plus CE-T Edition Python
disposent donc d'un
heap
avec exactement
19,968 Ko
de capacité.
Mais lorsque l'on accède à l'environnement
Python
, nombre de choses sont initialisées et ce
heap
n'est pas vide. Les dernières versions respectives
5.5.1
et ne nous offrent plus que
17,104 Ko
de libres sur le
heap
, alors que la version
5.4
de l'année dernière culminait à
19,408 Ko
.

Précisions que cet espace libre a de plus ici été amputé de par notre importation du module
gc
. Ce module n'étant hélas disponible que sur une minorité de
Pythonnettes
il va nous falloir procéder autrement, surtout si l'on souhaite obtenir des mesures comparables.

Donnons quelques éléments de taille en mémoire d'objets
Python
usuels, du moins sur les plateformes 32 bits que sont nos calculatrices :
  • pour un entier nul :
    24
    octets déjà...
  • pour un entier court non nul
    (codable sur 31 bits + 1 bit de signe)
    :
    28
    octets
  • pour un entier long :
    • 28
      octets
    • +
      4
      octets pour chaque groupe de 30 bits utilisé par son écriture binaire au-delà des 31 bits précédents
  • pour une chaîne:
    • 49
      octets
    • +
      1
      octet par caractère
  • pour une liste :
    • 64
      octets
    • +
      8
      octets par élément
    • + les tailles de chaque élément
Voici une fonction qui retourne la taille d'un objet selon ces règles :
Code: Select all
def size(o):
  t = type(o)
  s = t == str and 49 + len(o)
  if t == int:
    s = 24
    while o:
      s += 4
      o >>= 30
  elif t == list:
    s = 64 + 8*len(o)
    for so in o:
      s += size(so)
  return s


Une piste consiste alors à tenter de remplir le
heap
jusqu'à déclenchement d'une erreur, et retourner alors l'espace maximal que l'on a réussi à consommer. Voici justement une fonction en ce sens :
Code: Select all
def mem(v=1):
  try:
    l=[]
    try:
      l.append(0)
      l.append(0)
      l.append("")
      l[2] += "x"
      while 1:
        try:
          l[2] += l[2][l[1]:]
        except:
          if l[1] < len(l[2]) - 1:
            l[1] = len(l[2]) - 1
          else:
            raise(Exception)
    except:
      if v:
        print("+", size(l))
      try:
        l[0] += size(l)
      except:
        pass
      try:
        l[0] += mem(v)
      except:
        pass
      return l[0]
  except:
    return 0


L'appel mem(0) semble marcher comme souhaité, retournant une valeur qui peut comme prévu légèrement dépasser les
17,104 Ko
trouvés plus haut.

Mais voilà autre petit problème, le résultat n'est pas toujours le même, dépendant en effet de l'état du
heap
lors de l'appel. Rien que sur les résultats ci-contre, nous avons une marge d'erreur de 1 à 2%.

12725C'est beaucoup, en tous cas suffisamment pour inverser injustement des modèles au classement. Or cette année, nous tenons à être aussi précis que possible comme tu as pu le voir dès notre 1er épisode, afin justement de produire un classement aussi équitable que possible. ;)

Certes, on pourrait nettoyer ça avant chaque appel avec gc.collect(), mais ce ne serait pas juste puisque nous n'aurons pas cette possibilité sur nombre de modèles concurrents. Il nous faut donc trouver autre chose.

L'absence du module
gc
et donc de gc.collect() ne signifie absolument pas que le
heap
ne sera jamais nettoyé. C'est juste que nous ne contrôlons pas le moment où il le sera.

Et bien voici l'élément final du protocole de test que nous te proposons, avec une boucle répétant des appels mem(0), ce qui devrait finir par déclencher des nettoyages du
heap
, et te signalant à chaque fois que la valeur retournée bat ainsi un nouveau record :
Code: Select all
def testmem():
  m = 0
  while 1:
    t = mem(0)
    if t > m:
      m = t
      input(str(m))

testmem() signale au départ rapidement plusieurs nouveaux records d'occupation mémoire. Battre chaque nouveau record est de plus en plus difficile, et les nouveaux affichages nécessitent de plus en plus de temps. Nous arrêtons le test lorsque le dernier record n'aura pas pu être battu malgré 5 minutes écoulées depuis son affichage.

Nous aurions donc
17,233 Ko
disponibles sur le
heap
.

Mais ici encore lorsque nous réalisons notre appel, le
heap
a déjà été entâmé par l'importation de notre script de test.

Pas grave, il nous suffit tout simplement d'utiliser le module
gc
pour connaître la consommation
heap
de notre script. ;)

736
octets donc, qu'il nous suffira d'ajouter à toutes les valeurs obtenues dans ce qui suit.

Nous avons donc ici sur
TI-83 Premium CE Edition Python
et
TI-84 Plus CE-T Edition Python
17,233+0,736= 17,969 Ko
. Et entre nous, ce n'est franchement pas beaucoup.

Prenons maintenant l'ancienne
TI-83 Premium CE
munie du module externe
TI-Python
interdit aux examens français, mais restant utilisable en classe ainsi qu'aux évaluations si l'enseignant le permet.

Ce n'est pas la panacée mais c'est quand même sensiblement mieux, avec
19,496+0,736= 20,232 Ko
.

Conscient du problème de sous-dimensionnement de ce
heap
, a développé un
firmware
tiers
pour le module externe
TI-Python
.

Si tu l'installes tu bénéficieras donc d'un espace
heap
disponible nettement amélioré, avec
22,158+0,736= 22,894 Ko
.

C'est donc au-delà de la capacité
heap
de
19,968 Ko
trouvée plus haut pour le
firmware
officiel, mais c'est normal puisque l'on se rend compte
Lionel
a en effet passé la capacité
heap
à
22,912 Ko
.

1274412743Arrive maintenant la
NumWorks
. Depuis l'année dernière, nous passons de la version
12.2
à
14.4
. Enormément de choses ont été apportées par les mises à jour intermédiaires.

Et justement, le
heap
qui était à
15,557+0,736= 16,293 Ko
utilisables pour tes scripts, double à
31,485+0,736= 32,221 Ko
! :o

Mais la chose ne s'arrête pas là. Il est possible très facilement sur ta
NumWorks
d'installer un
firmware
tiers, . Basé sur le
firmware
officiel dont il suit les évolutions, il lui rajoute plein de fonctionnalités utiles et légitimes qui auront le gros avantage de rester disponible en mode examen ! :D

Sur la dernière édition matérielle
NumWorks N0110
,
Omega
permet notamment l'ajout d'applications. Plusieurs sont disponibles dont l'application de mathématiques intégrée
KhiCAS
par , enseignant chercheur à l'
Université de Grenoble
, une version adaptée aux plateformes nomades qui s'inspire de son propre logiciel de Mathématiques intégré
Xcas
, et en reprend notamment le moteur de calcul formel
GIAC
. :D

1274612747Et bien
Bernard
est justement en train de te préparer une mise à jour majeure de
KhiCAS
pour l'année scolaire
2020-2021
, déjà accessible en version de test. Au menu des nouveautés une sous-application tableur / feuille de calculs, ainsi que l'intégration d'un véritable interpréteur
MicroPython
! :#tritop#:

Et grosse surprise puisque nous bondissons ici à
39,747+0,736= 40,483 Ko
de
heap
disponible,
Bernard
ayant en effet eu la bonne idée de passer la capacité
heap
à
40 Ko
! :)

127571275512754Mais ce n'est pas tout,
KhiCAS
est notamment la seule solution
Python
sur calculatrices à te permettre de choisir toi-même la taille du
heap
, par défaut donc de
40 Ko
, et ce librement entre
16 Ko
et
64 Ko
, une formidable option pour estimer la consommation
heap
de tes projets ! :D

1275912758Passons donc ça à
64 Ko
, et effectivement nous obtenons maintenant un espace
heap
disponible de
63,660+0,736= 64,396 Ko
! :bj:

12748La
Casio Graph 35+E II
nous crève maintenant le plafond avec pas moins de
99,490+0,736= 100,226 Ko
de
heap
disponible dans son application
Python
officielle ! :D

Il existe aussi une application
Python
tierce pour les
Casio Graph
monochromes, . Elle est compatible avec les modèles suivants, mais hélas bloquée par le mode examen :

1275312750Sur les deux premiers nous nous envolons à pas moins de
257,026+0,736= 257,762 Ko
! :bj:

En effet selon le module
gc
, la capacité
heap
a ici été réglée à
258,048 Ko
.

1275212749Hélas, un bug toujours pas corrigé depuis l'année dernière fait que
CasioPython
reconnaît bêtement la
Graph 35+E II
comme un ancien modèle, n'y réservant alors qu'une capacité
heap
de
32,256 Ko
. :'(

Nous n'obtenons alors qu'un espace
heap
libre de
31,163+0,736= 31,899 Ko
, ici donc sans aucun intérêt par rapport à l'application
Python
officielle. :#non#:

12751La
Casio Graph 90+E
nous met maintenant en orbite avec un formidable
1031,713+0,736= 1032,449 Ko
soit
1,032 Mo
, de quoi développer de fantastiques projets ! :#tritop#:

Pour les
TI-Nspire CX II
, nous ne disposons hélas pas à ce jour de préversion de la mise à jour qui devrait sortir mi-septembre 2020 et rajouter la programmation
Python
.

Pour les anciens modèles
TI-Nspire CX
et
TI-Nspire
monochromes par contre, si non encore mis à jour en version
4.5.1
ou supérieure, il est possible de leur installer le
jailbreak
qui autorise à son tour par la suite l'installation d'applications tierces. :)
Attention toutefois, contrairement aux applications
Omega
pour
NumWorks
,
Ndless
fait hélas le choix de s'effacer totalement devant le mode examen ! :mj:

Les applications
Ndless
seront donc inutilisables, y compris donc les applications parfaitement légitimes comme
MicroPython
apportant des fonctionnalités disponibles en mode examen sur d'autres modèles. :'(


Une fois
Ndless
installé, on peut par exemple rajouter l'application qui nous fait littéralement quitter l'attraction terrestre avec pas moins de
2080,065+0,736= 2080,801 Ko
soit
2,081 Mo
! :#tritop#:

En creusant un petit peu grâce au module
gc
ici disponible, nous découvrons que la capacité
heap
est de
2,049 Mo
.


Mais pour les seules anciennes
TI-Nspire CX
, ce n'est pas tout. est également en train de préparer ici la même mise à jour majeure de l'application
KhiCAS
que pour
NumWorks
, avec feuille de calcul / tableur et véritable interpréteur
MicroPython
intégrés, également disponible en version de test ! :bj:

Nous sommes ici en retrait même si cela reste parfaitement honorable, avec
1023,812+0,736= 1024,548 Ko
soit
1,025 Mo
.

En effet la capacité
heap
n'est ici que de
1,025 Mo
selon le module
gc
.


Une mise à jour
HP Prime
rajoutant une application
Python
est dans les tuyaux. Aucune date de sortie communiquée à ce jour, mais une version intégrant cette fonctionnalité a été publiée par erreur en octobre 2019.
Cela a sûrement été corrigé depuis, mais cette vieille version est en pratique très instable. Nous te déconseillons fortement de l'installer dans le contexte d'évaluations.

Nous ne pourrons en l'état la retenir au classement, surtout que nous n'avons aucune garantie qu'elle sorte en 2020-2021, mais nous testons quand même lorsque possible afin de pouvoir t'estimer ce que vaudra la mise à jour en question.

Donc ici encore nous bénéficions d'un
heap
correctement dimensionné, avec
1017,692+0,736= 1018,428 Ko
soit
1,018 Mo
de disponibles sur le
heap
. :)

Le module
gc
nous apprend en effet que
HP
a réglé la capacité de son
heap
ici encore à
1,025 Mo
, exactement comme
Bernard
.




Résumé de nos mesures, avec donc l'espace
heap
Python
disponible à vide pour chacun des modèles :
  • en bas ne tient compte que des seules capacités d'origine officielles de la machine en mode examen sur les versions actuellement à notre disposition
  • en haut tient compte de toutes les possibilités évoquées pour d'autres situations
    (installation d'applications, mises à jour à venir, contexte hors mode examen...)
Image
User avatar
critorAdmin
Niveau 19: CU (Créateur Universel)
Niveau 19: CU (Créateur Universel)
Level up: 9.2%
 
Posts: 36074
Images: 9867
Joined: 25 Oct 2008, 00:00
Location: Montpellier
Gender: Male
Calculator(s):
Class: Lycée
YouTube: critor3000
Twitter: critor2000
Facebook: critor.ti
GitHub: critor

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby parisse » 09 Aug 2020, 07:52

Pour KhiCAS, il s'agit des valeurs par defaut, mais la taille du tas est configurable (depuis la configuration), sur la Numworks on peut atteindre une taille maximale de 64K.
User avatar
parisseVIP++
Niveau 12: CP (Calculatrice sur Pattes)
Niveau 12: CP (Calculatrice sur Pattes)
Level up: 10.6%
 
Posts: 2206
Joined: 13 Dec 2013, 16:35
Gender: Not specified

Online

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby critor » 09 Aug 2020, 08:14

Ah fort intéressant, merci. :)

Et je suppose qu'il y a une contrepartie sinon on aurait le maximum par défaut, surtout sur NumWorks ?
Image
User avatar
critorAdmin
Niveau 19: CU (Créateur Universel)
Niveau 19: CU (Créateur Universel)
Level up: 9.2%
 
Posts: 36074
Images: 9867
Joined: 25 Oct 2008, 00:00
Location: Montpellier
Gender: Male
Calculator(s):
Class: Lycée
YouTube: critor3000
Twitter: critor2000
Facebook: critor.ti
GitHub: critor

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby cent20 » 09 Aug 2020, 09:02

Superbe article qui se lit comme une enquête policière :#tritop#: !

Il manque peut-être deux trucs, pour l'année prochaine :


- Le lien entre le CPU utilisé et la mémoire totale maximum disponible, ainsi que le % de mémoire accordé au heap et donc la capacité du constructeur à exploiter correctement son hadware.
- La fragmentation de la mémoire et ses conséquences : l'année dernière, lors de l’exécution des scripts proposé ici pour évaluer la mémoire sur la NumWorks, on trouvait que le premier bloc mémoire faisait 4 ko puis plus tard après une mise à jour 8ko. Quels conséquences concrète cette fragmentation de la mémoire a t'-elle lors de l’exécution d'un script ?

Par exemple
:
- sur la NumWorks équipé d'un tas de 16 ko, quand le premier bloc de mémoire faisait 4 ko il était impossible de lancer un script de plus de 4ko, même si celui-ci ne créait que peu d'objets.
- sur la NumWorks équipé d'un tas de 32 ko, le premier bloc de mémoire fait 8 ko il était impossible de lancer un script de plus de 8ko.

Est-ce une coïncidence ou il y a-t-il un lien de causalité ?
Image
Enseignant de mathématiques et de spécialité NSI ( projets, tutos ...)
:favorite: NumWork, Python : démineur, snake, tétris
User avatar
cent20Premium
Niveau 13: CU (Calculateur Universel)
Niveau 13: CU (Calculateur Universel)
Level up: 24.3%
 
Posts: 499
Images: 38
Joined: 17 May 2012, 09:49
Location: Avignon
Gender: Male
Calculator(s):
Twitter: nsi_xyz

Online

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby critor » 09 Aug 2020, 09:28

parisse wrote:Pour KhiCAS, il s'agit des valeurs par defaut, mais la taille du tas est configurable (depuis la configuration), sur la Numworks on peut atteindre une taille maximale de 64K.


Voilà c'est testé, précisé, le diagramme est mis à jour, et ma feuille de calcul des scores du classement également. Merci de m'avoir fait découvrir cette option bien sympathique. :)

Sur TI-Nspire par contre, cette option bien que présente semble ignorée. Peu importe ce que je précise entre 256K
(par défaut)
et 4M, mes scripts détectent toujours 1M de
heap
.
Image
User avatar
critorAdmin
Niveau 19: CU (Créateur Universel)
Niveau 19: CU (Créateur Universel)
Level up: 9.2%
 
Posts: 36074
Images: 9867
Joined: 25 Oct 2008, 00:00
Location: Montpellier
Gender: Male
Calculator(s):
Class: Lycée
YouTube: critor3000
Twitter: critor2000
Facebook: critor.ti
GitHub: critor

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby M4x1m3 » 09 Aug 2020, 09:45

Btw je trouve bizarre que sur le classement de l'épisode 3 y ait examen / non-examen mais qu'ici y ait officiel / non-officiel. Je trouve que KhiCAS devrais être en bas vue que c'est utilisable en mode examen.

"Regression testing"? What's that? If it compiles, it is good, if it boots up it is perfect.
User avatar
M4x1m3Programmeur
Niveau 13: CU (Calculateur Universel)
Niveau 13: CU (Calculateur Universel)
Level up: 13.5%
 
Posts: 121
Images: 8
Joined: 13 Oct 2019, 21:10
Location: Bas-Rhin (67)
Gender: Male
Calculator(s):
Class: DUT Informatique
GitHub: M4xi1m3

Online

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby critor » 09 Aug 2020, 09:51

Le classement final se base sur les fonctionnalités officielles
(c'est-à-dire présentes à l'achat)
et utilisables en mode examen.

Par contre, pour chaque modèle seront mentionnées comme l'année dernière les possibilités d'améliorations avec les scores et liens associés.

Sinon, il serait bon d'arriver à régler le conflit Delta / Omega d'ici la rentrée. Parce que je teste avec une version KhiCAS qui n'est pas compatible Omega, et au classement final destiné aux acheteurs/utilisateurs je ne pourrai pas continuer à éluder ce point.

Cela m'embêterait beaucoup d'avoir à présenter les améliorations Omega et KhiCAS comme mutuellement exclusives, avec donc deux notes différentes. :'(
Et ce serait aussi fort dommage pour les utilisateurs d'avoir ainsi à choisir.
Image
User avatar
critorAdmin
Niveau 19: CU (Créateur Universel)
Niveau 19: CU (Créateur Universel)
Level up: 9.2%
 
Posts: 36074
Images: 9867
Joined: 25 Oct 2008, 00:00
Location: Montpellier
Gender: Male
Calculator(s):
Class: Lycée
YouTube: critor3000
Twitter: critor2000
Facebook: critor.ti
GitHub: critor

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby parisse » 09 Aug 2020, 10:38

critor wrote:Ah fort intéressant, merci. :)

Et je suppose qu'il y a une contrepartie sinon on aurait le maximum par défaut, surtout sur NumWorks ?

Le tas MicroPython est pris sur l'espace total de RAM utilisable par KhiCAS, soit un peu moins de 128K. Comme il faut de la place pour l'interface utilisateur (l'editeur de texte en particulier), mais aussi un minimum d'espace pour le noyau de calcul formel meme si on ne l'utilise pas directement (il est appele par le module graphic ou le module linalg/numpy ou le module arit de MicroPython mais aussi par le tableur, qui est lui-meme gourmand en memoire), j'ai mis une limite haute a 64K. C'est sur qu'une Numworks N0120 avec plus de RAM et de flash ca serait bienvenu ... c'est ce que je ne cesse de repeter depuis la sortie de la N0100.

Sur les incompatibilites KhiCAS/Omega, en attendant de comprendre precisement ce qui se passe, qu'est-ce qui empeche Omega d'utiliser exactement la meme appli External que Delta?
User avatar
parisseVIP++
Niveau 12: CP (Calculatrice sur Pattes)
Niveau 12: CP (Calculatrice sur Pattes)
Level up: 10.6%
 
Posts: 2206
Joined: 13 Dec 2013, 16:35
Gender: Not specified

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby M4x1m3 » 09 Aug 2020, 10:47

parisse wrote:Sur les incompatibilites KhiCAS/Omega, en attendant de comprendre precisement ce qui se passe, qu'est-ce qui empeche Omega d'utiliser exactement la meme appli External que Delta?


Pas grand-chose en sois. Ça ne nous dérange pas d'intégrer une version stable (donc quand vous serez sûre de plus avoir besoin de faire de modifs), et d'incrémenter le numéro de version de l'API (le passer à 3, parce que pour l'instant on est à 2 nous). Envoyez-nous un patch de l'app external quand ça sera prêt et on intègrera.

"Regression testing"? What's that? If it compiles, it is good, if it boots up it is perfect.
User avatar
M4x1m3Programmeur
Niveau 13: CU (Calculateur Universel)
Niveau 13: CU (Calculateur Universel)
Level up: 13.5%
 
Posts: 121
Images: 8
Joined: 13 Oct 2019, 21:10
Location: Bas-Rhin (67)
Gender: Male
Calculator(s):
Class: DUT Informatique
GitHub: M4xi1m3

Re: QCC 2020 épisode 4 : Python et tas (heap)

Unread postby Maxou09 » 09 Aug 2020, 11:10

C’est un régal que lire les exposés de Critor.

Il serait bon aussi de parler des modes d’emploi accessibles pour chaque calculatrices. Certaines ayant des fonctions/commandes différentes.

Merci
User avatar
Maxou09
Niveau 0: MI (Membre Inactif)
Niveau 0: MI (Membre Inactif)
Level up: 0%
 
Posts: 56
Joined: 13 Nov 2019, 15:06
Gender: Not specified
Class: Retraité

Next

Return to News Divers

Who is online

Users browsing this forum: No registered users and 28 guests

-
Search
-
Featured topics
Comparaisons des meilleurs prix pour acheter sa calculatrice !
Découvre les nouvelles fonctionnalités en Python de l'OS 5.2 pour les Nspire CX II
Découvre les nouvelles fonctionnalités en Python de l'OS 5.5 pour la 83PCE/84+C-T Python Edition
Omega, le fork étendant les capacités de ta NumWorks, même en mode examen !
1234
-
Donations / Premium
For more contests, prizes, reviews, helping us pay the server and domains...

Discover the the advantages of a donor account !
JoinRejoignez the donors and/or premium!les donateurs et/ou premium !


Partner and ad
Notre partenaire Jarrety Calculatrices à acheter chez Calcuso
-
Stats.
497 utilisateurs:
>485 invités
>6 membres
>6 robots
Record simultané (sur 6 mois):
6892 utilisateurs (le 07/06/2017)

-
Other interesting websites
Texas Instruments Education
Global | France
 (English / Français)
Banque de programmes TI
ticalc.org
 (English)
La communauté TI-82
tout82.free.fr
 (Français)