In general people prefer to use a merkle-tree claim system which puts the action on the user side and they would pay the gas fee. Also when the user claims the airdrop you directly know which address so you don't have to loop over all the addresses
If you do want to airdrop it directly, and not use on of the online systems that can already do this for you then i advice you to check out:
which contains some information about saving gas and as a bonus included an example of exactly what you want at the end.
transferFrom will check and update the approval many times. That is expensive and can be avoided by doing it only once, and than doing a loop of transfers
function bulkAirdropERC20(IERC20 _token, address[] calldata _to, uint256[] calldata _value) public {
uint256 length = _to.length;
require(length == _value.length, "Receivers and amounts are different length");
uint256 totalValue = 0;
for (uint256 i = 0; i < length; ++i) {
totalValue += _value[i];
}
require(_token.transferFrom(msg.sender, address(this), totalValue));
for (uint256 i = 0; i < length; ++i) {
require(_token.transfer(_to[i], _value[i]));
}
}
Other than the generally correct approach of executing an airdrop via Merkle Tree, already described here in a previous answer, there is an obvious optimization shouting out of your code.