I am quite familiar with GCC -O3 flag, but how it differs from -Os, in which situation we should prefer one over other?
-O3 optimizes for speed, whereas -Os optimizes for space. That means -O3 will give you a fast executable, but it may be rather large, and -Os gives you a smaller executable, but it might be slower.
Space and time efficiency is usually a trade-off. Faster algorithms tend to take up more space, where in-place algorithms (algorithms that don't increase the space usage) tend to be less efficient.
Usually modern computers have plenty of memory space, so -O3 is usually preferable. However if you're programing for something with low-ram (like a small device) you might prefer -Os