With these large lists, I can only guess at the why script would be fast or slow. But with REST API each call has to both get authenticated and JSON is even more string parsing. That's overhead that both API and scripting don't have, since auth is done once.
Some speculation... With some RSC script with 100K list of /ip/firewall/address-list/add's that runs from /system/script will be
parsed once from script, than executed, with auth being aligning the UNIX user id. If you want to get really dorky, scripting, config and CLI all all get converted some "s-expression like thing" that scripting typeof call "(code)":
:put [:parse "/ip/firewall/address-list/add list=mylist address=1.1.1.1"]
(evl /ip/firewall/address-list/addaddress=1.1.1.1;list=mylist)
So I view the (evl (())) things as the lowest level before any user thing hits RouterOS C code. And when you "run" a script it's first converted into a very long (evl (()())) as the first phase, and that's whole thing is handed off to just run. NOW... when you call REST... track your TCP, check /users each time, convert the JSON into the API "string sentences"....and API likely has same/similar string process to get a similar IL like (evl ()()).
Basically with REST, you'll add 100K auth calls, and 100K*2 (200K) extra string transformations when starting at REST.. API get you to 1 auth call, and 100K extra string transformation. CLI would also have a similar amount string transformation (once, so 100K), except it's only parsing it once, not per request (or API sentence) so there is no thread context switches when using /system/script.
Now... the corollary here... you may not want to drive the CPU to 100%... so may want to throttle anyway to avoid the CPU hit that could impact routing.
In terms of posting your code, feel free to do it here. Or, just start a new topic like "Scripting address-list with Python with example", or whatever, if you want a more organized presentation of it.