Why loops?
for(var/sometype/s in somelist)
//do something
The for...in loop is a very common and useful pattern for a lot of approaches in game design. What it does, is repeat a section of code for every instance of an object in the supplied list. This pattern is actually where I see a lot of attempts to write systems in DM go wrong. I'm not saying there's anything wrong with loops. They are useful. Use them. But you have to understand how DM works behind the scenes to really use this pattern to its fullest.
The for...in loop has three main components:
for(specifier in list)
action
1) The specifier. The specifier tells DM's interpreter what you are looking for inside of the supplied list.
2) The list. The list tells DM's interpreter where to look for the specifier.
3) The action. The action tells DM's interpreter what to do when something matching the specifier is found in the list.
You should be aware that behind the scenes, DM has to search every element of the supplied list in order to find every item in the list that matches the specifier. This means that the list actually does a bit more than you tell it to.
The larger the list that you supply to the for...in loop, the more items it has to check against the specifier. So even if the item doesn't match the specifier, the VM checking to see if it matches takes a very small amount of CPU time. This is all done for you automatically in DM's backing C code, so it's going to be a lot faster than doing it manually. However, there will be a point where a very immensely large list will really start to bog down the world when you iterate through it.
for...in world
world.contents is generally the largest list in any project. This means that supplying the world's contents to a for..in loop is almost always going to be the slowest thing you can do.
If you ever have a piece of code that has to search through the world to find something specific, odds are that you have used the least-optimal approach possible. Also, if you ever write a piece of code that has to search through any list looking for anything specific, odds are you are using the for..in pattern wrong in the first place. There are correct uses of this approach, but generally if you aren't calling the action block of the for...in pattern on every element of the supplied list, you are not taking advantage of proper design patterns. Again, this is not always the case, but it almost always is.
Let's look at some common examples and how to improve them.
mob
verb
GlobalChat(msg as text)
src << "You say: [msg]"
for(var/mob/M in world)
if(M!=src)
M << "[src]: [msg]"
This is a common Global Chat approach that you see in a lot of games. Unfortunately, it's not very efficient. Let's take a look at a better approach:
var
list/players = list()
mob
Login()
..()
players += src
Logout()
players -= src
src.loc = null
verb
WorldChat(msg as text)
var/list/receivers = players-src
src << "You say: [msg]"
receivers << "[src]: [msg]"
This approach doesn't make a major speed difference in small worlds, but the bigger your world gets, the faster this approach gets compared to the old one.
Now, you might be asking yourself: "How would I have known to do that?". The answer is simple. You should always store data that you are going to need again later somewhere. Searching through a list to find a series of items that match a particular specifier should be avoided, and specific items that need to be hung onto should be stored in list variables somewhere in your code for later use. It does increase the size of your code a little bit, and it's a little harder to design this pattern than just depending on for...in, but if you rely on for...in, your games will get progressively slower and slower until finally you are convinced that BYOND itself is slow.
This is not an avanced technique. This is a very basic design pattern that you should be learning very early in your programming career. BYOND is unique in that for...in abuse is rampant here. It's been spread as a bad habit by a lot of popular programming examples around here, and it's even made its way into Space Station 13, and just about every rip source that exists. It's not just bad programmers that do this. I see some of the very best DM developers misuse these patterns frequently.
And then there was one:
Let's look at another common bit of code that I see a lot. People use loops to update single objects in DM. For instance:
mob
Login()
client.screen += new/obj/screen_obj/something()
proc
UpdateSomething()
for(var/obj/screen_obj/something/s in client.screen)
s.icon_state = "updated"
This approach is very wasteful. It's immensely slow, and only gets slower the more stuff you pack into the client's screen. Let's fix it.
mob
player
var/tmp
obj/screen_obj/something/some_name
Login()
some_name = new()
client.screen += some_name
proc
UpdateSomething()
some_name.icon_state = "updated"
See how easy that is? Plus, no-loop, no CPU issues later. You can store the screen object in a variable and just use that variable handle rather than having to search through the screen list every time you want to use it. Hanging on to information that you are going to need to use later is not an advanced technique either. This is introductory stuff. As a rule of thumb: If you need the data again later, it should be stored in a variable in a sensible place. I catch self-described "DM pros" failing to store data properly and using loops to retrieve it all the time. Beware this type of design, because it's actually more work to do it that way, and it's slower by a huge margin.
Find a friend
Here's another common example of where people go wrong. When you need to find a specific object of a specific type. Here's an example of what you see done a lot:
mob
verb
Whisper(player as text,msg as text)
for(var/mob/M in world)
if(M.client && M.name==player)
src << "to [M]: [msg]"
M << "[src] whispers: [msg]"
return
src << "[player] not found."
We can improve this by storing the players in an associative list by their name. You might not think this will make a significant difference in speed, but you'd be surprised. Associative list indexing gives you constant O(1) time, while searching a list gives you O(n) linear time. Constant time is the absolute minimum threshold for optimization for a pattern you can get to. Grabbing an associative index does not require the underlying engine to search the whole list, thus it will be faster than searching the list in every instance.
var
list/player_names = list()
mob
Login()
..()
player_names[src.name] = src
Logout()
player_names.Remove(src.name)
verb
Whisper(player as text,msg as text)
var/mob/M = player_names[player]
if(M)
src << "to [M]: [msg]"
M << "[src] whispers: [msg]"
else
src << "[player] not found."
Associative lists are very useful for storing data, and they are extremely fast. You just have to remember to clean up unused references and keep in mind that any referenced object cannot be garbage collected, so always, always, always remember to do your cleaning.
Conclusion:
Loops and avoiding storing data you are going to need for later is why your code is slow 90% of the time. You need to look at what you are doing, and figure out when you NEED to use loops, and when you don't. There are a lot of ways to avoid excess processing of lists during iteration, and there are a lot of patterns to fix up problematic logic. I can't cover them all here. Just keep in mind that if there's a way to do it without the loop, do that. However, don't avoid loops without reason. If find yourself writing repetitive code, odds are that you want to use a loop instead. There is a time and a place for a loop. The trick is knowing when and how.
The datum.tag variable.
Tag is a useful way to store references to objects without interfering with the garbage collector. A tag is a unique string that you can use to identify an object in DM. You can retrieve the object by its tag later:
Internally, DM stores a Map (fancy word that means associative list) of all objects that have been given a unique tag. Storage in this list doesn't increment the reference count of the object. This is important, because in DM when an object is referenced (stored in a variable), the reference counter is increased for that object. When a reference to that object is cleared (either the variable being undefined or the variable being reassigned to another value), the reference counter is decreased. When the reference counter reaches zero for an object, the garbage collector catches it and frees up the object's memory. This can cause other objects referenced by the garbage object's variables to also drop into the garbage collector.
This is why I caution that you need to keep a good eye on your object references, because explicitly deleting objects using the del keyword is immensely slow. It's better to keep clean practices and clean up object references when they are no longer needed. Otherwise, you risk memory leaks.
The tag allows you to identify that you may need to get an object in the future, but also not bother hanging on to that object in memory if it's not needed anymore. The tag acts like a "weak reference", whereas putting an object into a variable is a "strong reference".
locate(tag) is actually really fast, because it doesn't need to search the entire world for the object. It just does a Map lookup on the internal tag table, which is basically the same thing as an associative list lookup. As I mentioned before, association is fast.
If there's a situation where you have an item that can be uniquely identified by a string easily, you can bypass maintaining extra global or instance associative lists by simply using the tag variable and the locate(tag) instruction.