Sounds to me like you have Vertical Synchronization enabled. This feature locks your frame rate to a fraction of your display’s refresh rate (1, ½, ⅓…) to prevent visual artifacting (specifically, “tearing”) and runaway frame rates (which may damage a GPU). A pretty good indication of this is a frame rate that jumps from 30 to 60 FPS and back, with nothing in-between.
Mirroring is affecting performance just enough to drop your frame rate below 60 FPS. Even if you would otherwise get 59 FPS, Vertical Synchronization would drop your frame rate to 30 FPS.
This may also result in input lag. Since your screen/cursor is now redrawn half (or one-third) as frequently, the delay between you moving the mouse and seeing the result on the screen would be twice or three-times as long.
Go into Portal’s video settings and Disable Vertical Synchronization. I don’t play Portal, but almost every game has that option. It may be named V-Sync.
You may need to switch from Windowed mode to Full Screen. Since macOS forces V-Sync for the main desktop, V-Sync may still be enforced while playing in a window.
To minimize the adverse effects of disabling V-Sync, many games offer the option to cap your frame rate. If that option is available, try setting a maximum of, eg. 90 FPS or even 60 FPS. This often offers the best of both worlds.
See if a combination of these solutions resolves your problem.
If you’d rather keep V-Sync enabled, many games offer an option to enable Triple-Buffering. This will help smooth out your frame rates (therefore minimizing sudden drops in frame rates). Enabling it may also result in increased input lag, though usually not as severe as one caused by dropping 30 FPS.
One last suggestion would be to try to extend your desktop onto the external display as a separate Space instead (possible with Yosemite and above, I believe). Does your use-case even require Mirroring? I’m not all that familiar with this on the Mac, but I don’t see why this wouldn’t be possible.
Bird’s-eye explanation of what’s causing your frame rate drops with Mirroring:
With Mirroring, your Mac has to display the same frame to your internal display and to your external display. In addition to causing your displays to wait on each other, this involves additional “work” including extra computational time and buffer management (to make sure they’re displaying the same thing at the same time), as well as the round-trip delay over your external cable or worse, Wi-Fi/Bluetooth.
While this is happening, your GPU is rendering the next frame in its internal buffer, but doesn’t send it to your display until it gets the OK (because of V-Sync). During this wait period, your GPU is effectively paused, not rendering additional frames (unless Triple- or N-Buffering is available), which further reduces your frame rate. Then, depending on where the slowest display is at in its refresh cycle, display of the next frame may be delayed yet again to the next fraction.
You have to keep in mind that in order to render your game at 60 FPS, your graphics subsystem only has 33ms to perform all that extra work (minus the time needed by the GPU to render those frames in the first place). The roundtrip to and from your external display alone may take a couple of milliseconds. Synchronization work is in and of itself costly (time-wise). It’s not unfathomable that synchronizing two displays takes more than 20 or 30 milliseconds.
Now, in the previous paragraph I was assuming that your GPU was ready with the next frame by the time your displays were done drawing the current one. Imagine that it wasn’t. Well, now the displays are starved, and they’re the ones that end up waiting on the GPU. You probably end up dropping to the next fraction of your slowest refresh rate. What might mean a 1-2 FPS drop V-Sync Off turns into a 30 FPS with it On.
So you’re trying to synchronize two displays (through Mirroring), then trying to synchronize THOSE against your GPU (through V-Sync). The result is potentially lots and lots and lots of waiting.
It is not unusual to experience some performance degradation when connecting subsystems with disparate performance levels. That is why buffers and asynchronous operations were created.
When synchronizing, a delay anywhere causes delays everywhere.
To maximize performance, best let everyone do their job on their own time.