Home Nieuws C-code schrijven zonder C te leren: de magie van PythoC

C-code schrijven zonder C te leren: de magie van PythoC

3
0
C-code schrijven zonder C te leren: de magie van PythoC

Interessante bibliotheek onlangs waar ik nog nooit van had gehoord.

PythonC is een Domain Specific Language (DSL)-compiler waarmee ontwikkelaars C-programma’s kunnen schrijven met behulp van de standaard Python-syntaxis. Het neemt een subset van statisch getypeerde Python-code en compileert deze rechtstreeks naar native machinecode via LLVM IR (Low Level Virtual Machine Intermediate Representation).

LLVM IR is een platformonafhankelijk codeformaat dat intern wordt gebruikt door het LLVM-compilerframework. De compiler vertaalt de broncode eerst naar LLVM IR’s en vervolgens converteert LLVM die IR’s naar machinecode die is geoptimaliseerd voor een specifieke CPU (x86, ARM, enz.).

De kernontwerpfilosofie van PythoC is: Runtime gelijk aan door Python ondersteunde C++-compilatietijd, en het heeft de volgende bijna unieke verkoopargumenten.

1. Maak een eigen uitvoerbaar bestand dat op zichzelf staat

In tegenstelling tot tools zoals Cython, die voornamelijk worden gebruikt om C-extensies te maken om bestaande Python-scripts te versnellen, kan PythoC volledig onafhankelijke, op zichzelf staande uitvoerbare bestanden in C-stijl produceren. Eenmaal gecompileerd, heeft het resulterende binaire bestand geen Python-interpreter of garbage collector nodig om te worden uitgevoerd.

2. Heeft controle op laag niveau met Python-syntaxis

PythoC weerspiegelt de mogelijkheden van C, maar verpakt ze in een schonere Python-syntaxis. Om dit te bereiken wordt gebruik gemaakt van de eigen typehints van de machine, en niet van de standaard dynamische typen van Python.

  • Primitief: i32, i8, f64, enz.
  • Geheugenstructuur: Pointers (ptr(T)), arrays (array(T, N)) en structs (gemaakt door standaard Python-klassen te decoreren).
  • Handmatig geheugenbeheer: Omdat het standaard geen garbage collector gebruikt, is het geheugenbeheer expliciet, zoals in C. Het biedt echter moderne optionele beveiligingscontroles, zoals lineair type (wat ervoor zorgt dat elke toewijzing expliciet ongedaan wordt gemaakt om lekken te voorkomen) en soort verfijning (om validatiecontroles tijdens het compileren te implementeren).

Python als metaprogrammeermachine

Een van de krachtigste functies van PythoC is de verwerking van compilatiestappen. Omdat de compileeromgeving eenvoudigweg Python is, kunt u standaard Python-logica gebruiken om uw PythoC-code te genereren, manipuleren en specialiseren voor het compileert naar LLVM. Het biedt zeer flexibele mogelijkheden voor het genereren van code tijdens het compileren (vergelijkbaar met C++-sjablonen, maar aangestuurd door pure Python).

Klinkt veelbelovend, maar komt de werkelijkheid overeen met wat jij denkt? Oké, laten we deze bibliotheek in actie zien. Het installeren ervan is eenvoudig, zoals bij de meeste Python-bibliotheken, het is gewoon een pip-installatie zoals deze:

pip install pythoc

Het kan echter beter zijn om een ​​goede ontwikkelomgeving op te zetten waarin u uw verschillende projecten kunt scheiden. In mijn voorbeeld heb ik het UV-hulpprogramma gebruikt, maar gebruik de methode die voor u het handigst is. Typ de volgende opdracht in uw opdrachtregelterminal.

C:Usersthomaprojects> cd projects
C:Usersthomaprojects> uv init pythoc_test
C:Usersthomaprojects> cd pythoc_test
C:Usersthomaprojectspythoc_test> uv venv --python 3.12
C:Usersthomaprojectspythoc_test> .venvScriptsactivate
(pythoc_test) C:Usersthomaprojectspythoc_test> uv pip install pythoc

Eenvoudig voorbeeld

Om PythoC te gebruiken, definieert u een functie met behulp van een specifiek enginetype en tagt u deze met de PythoC-compiler decorateur. Er zijn twee manieren om uw PythoC-code uit te voeren. Je kunt de gecompileerde bibliotheek op deze manier rechtstreeks vanuit Python aanroepen:

from pythoc import compile, i32

@compile
def add(x: i32, y: i32) -> i32:
    return x + y

# Can compile to native code
@compile
def main() -> i32:
    return add(10, 20)

# Call the compiled dynamic library from Python directly
result = main()
print(result)

Voer het dan zo uit.

(pythoc_test) C:Usersthomaprojectspythoc_test>python test1.py

30

Of u kunt een zelfstandig uitvoerbaar bestand maken dat onafhankelijk van Python kan worden uitgevoerd. Gebruik hiervoor code als deze.

from pythoc import compile, i32

@compile
def add(x: i32, y: i32) -> i32:
    print(x + y)
    return x + y

# Can compile to native code
@compile
def main() -> i32:
    return add(10, 20)

if __name__ == "__main__":
    from pythoc import compile_to_executable
    compile_to_executable()

Wij voeren het op dezelfde manier uit.

(pythoc_test) C:Usersthomaprojectspythoc_test>python test4.py

Successfully compiled to executable: buildtest4.exe
Linked 1 object file(s)

Deze keer zien we geen uitvoer. In plaats daarvan maakt PythoC een build-map onder uw huidige map en maakt daar vervolgens een uitvoerbaar bestand aan dat u kunt uitvoeren.

(pythoc_test) C:Usersthomaprojectspythoc_test>dir buildtest4*
 Volume in drive C is Windows
 Volume Serial Number is EEB4-E9CA

 Directory of C:Usersthomaprojectspythoc_testbuild

26/02/2026  14:32               297 test4.deps
26/02/2026  14:32           168,448 test4.exe
26/02/2026  14:32               633 test4.ll
26/02/2026  14:32               412 test4.o
26/02/2026  14:32                 0 test4.o.lock
26/02/2026  14:32         1,105,920 test4.pdb

We kunnen het bestand test4.exe net als elk ander uitvoerbaar bestand uitvoeren.

(pythoc_test) C:Usersthomaprojectspythoc_test>buildtest4.exe

(pythoc_test) C:Usersthomaprojectspythoc_test>

Maar wacht even. In onze Python-code vragen we expliciet om het resultaat van de optelling af te drukken, maar we zien geen enkele uitvoer. Wat is er gebeurd?

Het antwoord is dat de ingebouwde print()-functie van Python afhankelijk is van de Python-interpreter die op de achtergrond draait om erachter te komen hoe objecten moeten worden weergegeven. Omdat PythoC dat allemaal verwijdert om een ​​klein, zeer snel native uitvoerbaar bestand te maken, wordt de print-instructie verwijderd.

Om in native binair naar het scherm af te drukken, moet u de standaard C-bibliotheekfunctie gebruiken: printf.

Hoe printf te gebruiken in PythoC

In C (en dus in PythoC) vereisen afdrukvariabelen formaatspecificaties. U schrijft een tekenreeks met een tijdelijke aanduiding (zoals %d voor een decimaal geheel getal) en geeft vervolgens de gewenste variabelen door aan die tijdelijke aanduiding.

Zo werkt u onze code bij om de C printf-functie te importeren en correct te gebruiken:

from pythoc import compile, i32, ptr, i8, extern

# 1. Tell PythoC to link to the standard C printf function
@extern
def printf(fmt: ptr(i8), *args) -> i32:
    pass

@compile
def add(x: i32, y: i32) -> i32:
  
    printf("Adding 10 and 20 = %dn", x+y)
    return x + y

@compile
def main() -> i32:
    result = add(10, 20)
    
    # 2. Use printf with a C-style format string. 
    # %d is the placeholder for our integer (result).
    # n adds a new line at the end.
   
    
    return 0

if __name__ == "__main__":
    from pythoc import compile_to_executable
    compile_to_executable()

Als we nu de bovenstaande code opnieuw uitvoeren en het resulterende uitvoerbare bestand uitvoeren, zal onze uitvoer zijn zoals we verwachten.

(pythoc_test) C:Usersthomaprojectspythoc_test>python test5.py
Successfully compiled to executable: buildtest5.exe
Linked 1 object file(s)

(pythoc_test) C:Usersthomaprojectspythoc_test>buildtest5.exe
Adding 10 and 20 = 30

Is het echter de moeite waard?

Alle dingen waar we het over hebben gehad zullen alleen nuttig zijn als we echte snelheidsverbeteringen in onze code zien. Laten we voor ons laatste voorbeeld eens kijken hoe snel ons gecompileerde programma kan worden vergeleken met een soortgelijk programma in Python, en dat zal onze vraag definitief beantwoorden.

Ten eerste: gewone Python-code. We zullen recursieve Fibonacci-berekeningen gebruiken om langlopende processen te simuleren. Laten we het veertigste Fibonacci-getal berekenen.

import time

def fib(n):
    # This calculates the sequence recursively
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

if __name__ == "__main__":
    print("Starting Standard Python speed test...")
    
    start_time = time.time()
    
    # fib(38) usually takes around 10 seconds in Python, 
    # depending on your computer's CPU.
    result = fib(40) 
    
    end_time = time.time()
    
    print(f"Result: {result}")
    print(f"Time taken: {end_time - start_time:.4f} seconds")

Ik kreeg dit resultaat toen ik de bovenstaande code uitvoerde.

(pythoc_test) C:Usersthomaprojectspythoc_test>python test6.py
Starting Standard Python speed test...
Result: 102334155
Time taken: 15.1611 seconds

Nu voor de op PythoC gebaseerde code. Nogmaals, net als de print-instructie in het vorige voorbeeld, kunnen we niet alleen de reguliere importtimingrichtlijn van Python gebruiken voor onze timings. In plaats daarvan moeten we standaard timingfuncties rechtstreeks van de programmeertaal C lenen: Uur(). We definiëren het op dezelfde manier als de printf-instructie die we eerder gebruikten.

Hier is een bijgewerkt PythoC-script met een ingebouwde C-timer.

from pythoc import compile, i32, ptr, i8, extern

# 1. Import C's printf
@extern
def printf(fmt: ptr(i8), *args) -> i32:
    pass

# 2. Import C's clock function
@extern
def clock() -> i32:
    pass

@compile
def fib(n: i32) -> i32:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

@compile
def main() -> i32:
    printf("Starting PythoC speed test...n")
    
    # Get the start time (this counts in "ticks")
    start_time = clock()
    
    # Run the heavy calculation
    result = fib(40)
    
    # Get the end time
    end_time = clock()
    
    # Calculate the difference. 
    # Note: On Windows, 1 clock tick = 1 millisecond.
    elapsed_ms = end_time - start_time
    
    printf("Result: %dn", result)
    printf("Time taken: %d millisecondsn", elapsed_ms)
    
    return 0

if __name__ == "__main__":
    from pythoc import compile_to_executable
    compile_to_executable()

Mijn output deze keer is:

(pythoc_test) C:Usersthomaprojectspythoc_test>python test7.py
Successfully compiled to executable: buildtest7.exe
Linked 1 object file(s)

(pythoc_test) C:Usersthomaprojectspythoc_test>buildtest7.exe
Starting PythoC speed test...
Result: 102334155
Time taken: 308 milliseconds

En in dit kleine voorbeeld zien we, ook al is de code wat complexer, een reëel voordeel van het gebruik van een gecompileerde taal zoals C. Onze uitvoering is 40x sneller dan vergelijkbare Python-code. Niet zo erg.

Voor wie is PythoC bedoeld?

Ik zie drie hoofdgebruikerstypen voor PythoC.

1/ Zoals we in de Fibonacci-snelheidstest hebben gezien, kan standaard Python langzaam zijn bij het zware rekenwerk. PythoC kan nuttig zijn voor elke Python-ontwikkelaar die natuurkundige simulaties, complexe algoritmen of aangepaste gegevensverwerkingspijplijnen maakt die prestatieknelpunten ervaren.

2/ Programmeurs die nauw samenwerken met computerhardware (zoals het bouwen van game-engines, het schrijven van stuurprogramma's of het programmeren van kleine IoT-apparaten) schrijven meestal in C omdat ze het geheugen van de computer handmatig moeten beheren.

PythoC kan deze ontwikkelaars aanspreken omdat het dezelfde handmatige geheugencontrole biedt (met behulp van pointers en native types), maar hen in staat stelt Python te gebruiken als een “metaprogrammeer”-engine om schonere, flexibelere code te schrijven voordat deze tot op hardwareniveau wordt gecompileerd.

3/ Als u een nuttig Python-script schrijft en dit met een collega wilt delen, moet de collega meestal Python installeren, een virtuele omgeving opzetten en uw afhankelijkheden downloaden. Dit kan lastig zijn, vooral als de doelgebruiker niet erg IT-vaardig is. Met PythoC kan iedereen, zodra u het uitvoerbare bestand van C hebt gecompileerd, het uitvoeren door simpelweg op het bestand te dubbelklikken.

En dat is niet voor wie het bedoeld is

De keerzijde van het bovenstaande is dat PythoC misschien niet het beste hulpmiddel is voor webontwikkelaars, omdat het prestatieknelpunt meestal in de netwerk- of databasesnelheid ligt, en niet in de CPU-berekeningssnelheid.

Evenzo, als u al een gebruiker bent van een geoptimaliseerde bibliotheek zoals NumPy, zult u ook niet veel voordeel zien.

Samenvatting

Dit artikel laat je kennismaken met een relatief nieuwe en onbekende PythoC-bibliotheek. Hiermee kun je Python gebruiken om supersnelle, zelfuitvoerende C-code te maken.

Ik geef verschillende voorbeelden van het gebruik van Python en de PythoC-bibliotheek om uitvoerbare C-programma's te genereren, waaronder een voorbeeld dat de ongelooflijke snelheid laat zien van het uitvoeren van een uitvoerbaar programma gegenereerd door de PythoC-bibliotheek, vergeleken met een standaard Python-programma.

Een van de problemen die je tegenkomt is dat Python-imports niet worden ondersteund in PythoC-programma's, maar ik heb ook laten zien hoe je dit kunt omzeilen door ze te vervangen door ingebouwde C-equivalenten.

Ten slotte bespreek ik wie volgens mij de typen Python-programmeurs zijn die het nuttig zouden kunnen vinden om PythonC in hun werklasten te gebruiken, en degenen die dat niet willen.

Ik hoop dat dit je interesse wekt om te zien welke soorten gebruiksscenario's je kunt gebruiken met PythoC. Je kunt meer leren over deze nuttige bibliotheek door de GitHub-repository te bekijken via de volgende link.

https://github.com/1flei/PythoC

Nieuwsbron

LAAT EEN REACTIE ACHTER

Vul alstublieft uw commentaar in!
Vul hier uw naam in