Viper compiler documentation? #14297
Replies: 13 comments 20 replies
-
That's pretty much it, I'm afraid. There are a couple of slightly helpful threads on the old forum:
I don't use it, since it's not available on all of the boards I use (such as esp32c3) |
Beta Was this translation helpful? Give feedback.
-
This discussion has some nice CRC viper code (together with A google search for "micropython viper examples" spit out: this and this. The overhead py>viper>py is there, so only lengthy calculations really benefit from viper. As Steward (@scruss) told viper is not implemented on all architectures. On the RISC one of esp32c series obviously not yet. |
Beta Was this translation helpful? Give feedback.
-
See also this and this - old posts that still provide interesting background. |
Beta Was this translation helpful? Give feedback.
-
Calling a viper function seems to have similar overhead as a MicroPython function. If it has to be very fast, it would be best to minimize calls. Additional arguments seem to have little overhead. Here are some results I found by trying out viper code, but I don't have access to more documentation, so I may be wrong on some points. My tests were on a ESP32 and ESP32-S3, I don't know if everything applies to all ports. It seems that viper really shines with integer variables (32 bits, 16 bits and 8 bits). All I believe it's best to be aware at all times that For the viper compiler, it's best to declare all variables explicitly, same with parameters and the return type. You declare viper int with type hints, for example: @micropython.viper
def myfunction(a:int)->float:
j:int
x:int = 1
return 1.0 When you pass an array.array("I", ...) to a viper function, you can receive that with a viper ptr32, which is a pointer to the memory of the array. Pointers are like C pointers in that they are very fast, but you get no bounds checking. This enables to write stuff like this: from array import array
from uctypes import addressof
@micropython.viper
def myfun(b:ptr32)->int:
a = array("i", (11,22,33,44))
# Edit: this is slow: x:ptr32 = ptr32(addressof(a))
# Edit: this is better:
x:ptr32 = ptr32(a)
j:int = 0
s:int = 0
while j < 4:
s += x[j] + b[j]
j += 1
return s
b = array("i", (55,66,77,88))
print(myfun(b)) Viper ptrs can be cast to a a = array("i", (11,22,33,44))
len_of_array:int = 4
x:ptr32 = ptr32(addressof(a))
pointer_to_second_half_of_a: ptr32 = ptr32(uint(x) + (int(len(a))//2)*4 ) (edit) Note that since the array element length is 4 bytes, you have to multiply by 4 yourself. Reading and writing memory addresses directly also enables some very fast stuff with GPIO registers or other I/O functions. range() does work under viper, so you could write: '''for x in range(10)``` but it's faster to use a while loop:
Viper Somewhere the docs state a maximum of 4 arguments for a viper function, that seems not to be a restriction anymore. Just for fun, I wrote an integer FFT in viper code, see here: https://github.com/bixb922/crank-organ/blob/main/src/fft_int.py, as a longer and real example of viper code. The original C code is still there and commented, so you can compare viper to C. It takes a bit less than 30 milliseconds for 1024 data points, compared with 115 milliseconds of a @micropython.native version and about 320 milliseconds of a simple and straightforward algorithm in plain MicroPython. Please note: if you really want to do FFT, use Please correct me if something is wrong. I love to learn something new. |
Beta Was this translation helpful? Give feedback.
-
No, I wasn't aware of that. I just tested that and interestingly,
Ahh, now I found #8086, interesting!
I'll be glad if this can be of any help. Perhaps for the wiki? In that case I'd like all the feedback possible to complete this a bit. |
Beta Was this translation helpful? Give feedback.
-
I hope it helps, I am sometimes unsure myself if I observed correctly :-) int, uint, ptr32, ptr16 and ptr8 are much like casts in the C language.
Yes, ptr32 will return the first 4 bytes interpreted as an unsigned int, whatever the memory at ptr32 holds, be it a byte array or a 32 bit signed array. No conversion is done. The int() and uint() are just a cast, they just slam the viper data type on the 4 bytes.
Both return a 32 bit unsigned int (a viper uint). In the example, x[0] will always be 0-255.
This also can be written as x:ptr8 = ptr8(array), it's equivalent and faster. I'm sorry about the error in my previous post.
Again, it helps thinking about viper in terms of equivalence to the C language, much of viper is modeled after C. |
Beta Was this translation helpful? Give feedback.
-
I wrote all what I could find on the topic MicroPython viper here: https://github.com/bixb922/viper-docs Please report any error you find or clarification that should be needed. |
Beta Was this translation helpful? Give feedback.
-
I think there are a couple of errors re integer sizes:
Should read
Is this actually correct? I would expect a 30 bit integer - i.e. a MicroPython small int. In which case the range is As a general point, now that the doc exists in two places, how should we issue non-contentious corrections? I've spotted a few typos - should we edit the Wiki or raise a PR? |
Beta Was this translation helpful? Give feedback.
-
I took the liberty to edit the wiki page directly. I suggest that the developers change the permissions at some point, so the wiki can be maintained via PR. Viper int range typo corrected, should read Small int/viper integer constant range: @peterhinch you are correct, the range is
What do you mean by "two places"? If you mean my repository, I renamed it to https://github.com/bixb922/viper-examples to avoid confusions and deleted the README.md there, in order to have only one copy of the document online. I felt that it could be useful to keep the examples there online. I have been using those examples to find out how viper works.
@andrewleech? I am happy to do the corrections, but for many small changes, perhaps raising a PR is simpler than explaining the changes to someone else. There is room for improvement. |
Beta Was this translation helpful? Give feedback.
-
@SteKme I was not sure. Good questions btw.. So I tried it out. Here is the code: from array import array
from uctypes import addressof, bytes_at
# from https://github.com/orgs/micropython/discussions/14297
# 1. I wonder is it possible to return just a primitive type from viper, that is int, uint?
#
# 2. If an array (bytearray) gets allocated within viper, is it possible to return reference to it?
# (and what return type to use...)
#
# 3. Is it possible to return something else like None, boolean tuple?
# ----- 1. ------
@micropython.viper
def vip_in2uint(x: int) -> uint:
return uint(x)
print(vip_in2uint(-5)) # 4294967291 <---- yes
# ----- 2. ------
@micropython.viper
def vip_retarr() -> ptr32:
a = array('i', [5432, 1234, 0001, -2321]) # in CPython: SyntaxError: leading zeros in decimal
aadr = ptr32(a) # integer literals are not permitted; use an 0o prefix for octal integers
return aadr
adr = vip_retarr()
print(adr) # 536967184 <---- yes
adr_bytes = bytes_at(adr, 16)
print(adr_bytes) # b'8\x15\x00\x00\xd2\x04\x00\x00\x01\x00\x00\x00\xef\xf6\xff\xff'
# we have not uctypes.cast in Micropython
for i in range(4):
print(int.from_bytes(adr_bytes[4*i:(4*i+4)], 'little'))
# 5432 # <--- the integers in the array
# 1234
# 1
# 4294964975 # <--- the sign is lost (same in CPyton)
# ----- 3. ------
@micropython.viper
def vip_tuple1a():
return (2, 3)
print(vip_tuple1a()) # (2, 3) <---- yes
@micropython.viper
def vip_tuple1b():
return (True, None)
print(vip_tuple1b()) # (True, None) <---- yes
# ----- we also try out negation of integers and observe an error here -----
@micropython.viper
def vip_tuple2a():
a:int = int(-5)
return (a, 2)
print(vip_tuple2a()) # (-5, 2) <---- yes
@micropython.viper
def vip_tuple2b():
a:int = int(-5)
return (a, -a)
print(vip_tuple2b()) # (-5, 5) <---- yes
@micropython.viper
def vip_tuple2c():
a:int = int(-5)
b = -a
return (a, b)
print(vip_tuple2c()) # (5, 5) <---- oops , found an error !!!!, should be (-5, 5)
@micropython.viper
def vip_tuple2d():
a:int = int(-5)
return (a, -a, a)
print(vip_tuple2d()) # (-5, 5, 5) <--- error!!!!, should be (-5, 5, -5) IIrc many of those were not possible yet recently. It seems viper is improved permanently, without much advertising. |
Beta Was this translation helpful? Give feedback.
-
Wiki updated, thanks for that information and the tests! |
Beta Was this translation helpful? Give feedback.
-
nice! I added something about bools, @rkompass thank you for reading the code and writing those examples! Any comment is welcome! |
Beta Was this translation helpful? Give feedback.
-
I take that serious, man:-) So there is an addition regarding speed: TLDR: Use a while loop instead of You have the first rule mentioned in the wiki, but not the second. And it's about speed. from array import array
from time import ticks_us, ticks_diff
a = array('i', (2*i+1 for i in range(10000)))
@micropython.viper
def vip_add1(p: ptr32, l: int) -> int:
r:int = 0
for k in range(l): # range() works, but is not fastest
r += p[k] # accessing with p[i]
return r
@micropython.viper
def vip_add2(p: ptr32, l: uint) -> int:
r:int = 0
k:uint = uint(0)
while k < l: # a while loop is faster
r += p[k] # accessing with p[i]
k += 1
return r
@micropython.viper
def vip_add3(p: ptr32, l: uint) -> int:
r:int = 0
pstop: uint = uint(p) + 4*l
while uint(p) < pstop: # directly looping the pointer to the array and indexing
r += p[0] # with p[0] is faster than accessing with p[i]
p = ptr32(uint(p) + 4) # casts necessary because the types are fixed in viper
return r
t0 = ticks_us()
sum1 = vip_add1(a, len(a)) # --> 100000000
t1 = ticks_us()
sum2 = vip_add2(a, len(a)) # --> 100000000
t2 = ticks_us()
sum3 = vip_add3(a, len(a)) # --> 100000000
t3 = ticks_us()
print('vip_add1:', sum1, 'time:', ticks_diff(t1, t0)/10000, 'µs per it.') # --> 0.7064 µs
print('vip_and2:', sum2, 'time:', ticks_diff(t2, t1)/10000, 'µs per it.') # --> 0.5767 µs
print('vip_and3:', sum3, 'time:', ticks_diff(t3, t2)/10000, 'µs per it.') # --> 0.3635 µs |
Beta Was this translation helpful? Give feedback.
-
Hi,
please is there any documentation on viper compiler aside of this very short text?
https://docs.micropython.org/en/v1.9.3/pyboard/reference/speed_python.html#the-viper-code-emitter
Some examples available perhaps? I can't find much no matter how hard I google, unfortunately.
Also, calling this viper function from other, either plain or native compiled python - is there an extra overhead when doing so, i.e. is py>viper>py set of function calls more cpu costly than calling py>py>py functions? (this is to understand if viper is meaningful for even rather short pieces of code, or pays off only for code that is executed for a longer time)... reminds me of NUMBA for cpython, where this context switching from PY to Numba function is pretty costly if I remember this right.
Thanks so much in advance,
Stepan
Beta Was this translation helpful? Give feedback.
All reactions